ターミナルでKoT稼働時間を確認するkotツールを二要素認証に対応させてみた

ターミナルでKoT稼働時間を確認するkotツールを二要素認証に対応させてみた

弊社のKingOfTimeに二要素認証設定が有効となり、よりセキュアとなりました。が、ローカルで稼働させていたKoT稼働時間確認ツールは想定外の事態です。頑張って対応してみました。
Clock Icon2025.02.22

セキュリティ向上の一環にて、弊社環境でのKingOfTimeに二要素認証が設定されました。普通に使う分では1Passwordがやってくれるのですが、問題はターミナル上で稼働時間確認に使っていたツールの動作です。

以前スクレイピングの動作を正常にするために弄ったこともありましたが、今回は単なるHTML構造の取得変更程度には収まりません。

あれこれと弄ったところ想定していた動作にまで至ったので、修正点含めてのエントリーとなります。

二要素認証キーを渡す

一番の変更点は二要素認証キーを渡すところです。kotはpoetryで管理されたプロジェクト且つDocker上で動作するという構造になっています。Dockerfileそのものには引数で渡す手もありますが、問題はpoetryです。

poetry runあるいはpoetry shellで動作した場合、環境変数は接頭語としてPOETRY_が必要との記載があります。

これをDockerfile経由で渡すことになりますが、動作検証が予想以上に難儀なものとなりました。os.environ.getにて取得しても空になる状態です。これは接頭語をつけても外しても同じ。

よりシンプルにできないかと考え、思い切ってログイン情報とまとめることにしました。config.yaml内に二要素認証キーを追加してビルド、実行というわけです。

結果として動作しましたが、これまでdocker-compose経由で実行していた状態からpoetry runによる実行へと変わりました。

二要素認証のログイン対応修正

TOP_URLのドメインは実際にご利用のもので書き換えてください。

--- a/kot/common/config.py
+++ b/kot/common/config.py
@@ -11,7 +11,7 @@ IS_AWS_LAMBDA_RUNTIME: bool = os.getenv("AWS_LAMBDA_FUNCTION_NAME") is not None
 class Account:
     id: str
     password: str
-
+    tfa: str

 @dataclass
 class Slack:
@@ -37,6 +37,7 @@ class Config:
     account:
         id: id
         password: passaword
+        tfa: tfa
     scrapekot:
         slack:
             webhook_url: url
@@ -75,6 +76,7 @@ def generate_config(d: dict[str, Any]) -> Config:
         account=Account(
             id=d["account"]["id"],
             password=d["account"]["password"],
+            tfa=d["account"].get("tfa", ""),
         ),
         scrapekot=ScrapeKOT(
             slack=Slack(

--- a/kot/scrapekot/crawl.py
+++ b/kot/scrapekot/crawl.py
@@ -2,13 +2,14 @@ from dataclasses import dataclass

 from kot.common.crawl import BaseCrawler

-TOP_URL = "https://s3.kingtime.jp/admin"
+TOP_URL = "https://s2.ta.kingoftime.jp/admin"

 @dataclass
 class CrawlerParams:
     account_id: str
     account_password: str
+    account_tfa: str

 @dataclass
@@ -30,8 +31,12 @@ class Crawler(BaseCrawler):
             self.browser.send('//*[@id="login_password"]', params.account_password)
             # ログイン
             self.browser.click('//*[@id="login_button"]')
-            # ログイン成功したか確認
             url = self.browser.current_url
+            if params.account_tfa != "":
+                self.browser.send('//*[@id="authentication_code"]', params.account_tfa)
+                self.browser.click('//*[@id="app_auth_login_button"]')
+                url = self.browser.current_url
+            # ログイン成功したか確認
             if url == TOP_URL:
                 raise Exception("login failed")
             # ソースを取得

+++ b/kot/service.py
@@ -54,6 +54,7 @@ def scrape_kot(params: ScrapeKOTParams) -> str:
         crawler_params = ScrapeKOTCrawlerParams(
             account_id=cfg.account.id,
             account_password=cfg.account.password,
+            account_tfa=cfg.account.tfa,
         )
         slack_client_params = ScrapeKOTSlackClientParams(
             slack_webhook_url=cfg.scrapekot.slack.webhook_url,
@@ -90,6 +91,7 @@ def punch_myrecorder(params: MyRecorderParams) -> None:
         crawler_params = MyRecorderCrawlerParams(
             account_id=cfg.account.id,
             account_password=cfg.account.password,
+            account_tfa=cfg.account.tfa,
             command=params.command,
             message=params.message,
             yes=params.yes,

--- a/kot/service.py
+++ b/kot/service.py
@@ -54,6 +54,7 @@ def scrape_kot(params: ScrapeKOTParams) -> str:
         crawler_params = ScrapeKOTCrawlerParams(
             account_id=cfg.account.id,
             account_password=cfg.account.password,
+            account_tfa=cfg.account.tfa,
         )
         slack_client_params = ScrapeKOTSlackClientParams(
             slack_webhook_url=cfg.scrapekot.slack.webhook_url,
@@ -90,6 +91,7 @@ def punch_myrecorder(params: MyRecorderParams) -> None:
         crawler_params = MyRecorderCrawlerParams(
             account_id=cfg.account.id,
             account_password=cfg.account.password,
+            account_tfa=cfg.account.tfa,
             command=params.command,
             message=params.message,
             yes=params.yes,

config.yamlにaccount/tfaを追加します

account:
  id: 
  password: 
  tfa: 
scrapekot:
  slack: 
    webhook_url: 
    channel: 
    icon_emoji: 
    username: 
myrecorder: 
  slack:  
    webhook_url: 
    channel: 
    icon_emoji: 
    username: 

実行用のスクリプトは以下の通り。

#/bin/sh

gsed -i "s/^  tfa:.*$/  tfa: $1/" config.yaml
poetry run invoke build
poetry run invoke scrapekot
sh kot_check.sh XXXXXX

二要素認証キーを取得後、期限が切れる前にビルドして実行する必要があります。キーが新しくなった直後であれば恐らく問題ないでしょう。

あとがき

1password-cliから引っ張って実行する手も考えましたが、キーそのものの期限が分からないのがネックです。ブラウザの1Passwordアドオンから期限更新直後にコピペするのが安牌としました。

同じく二要素認証によりkotツールが動作できずに悩んでいた人の参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.