2024年5月2日木曜日

Mac で gcloud コマンドのタブ補完を有効にする方法

Mac で gcloud コマンドのタブ補完を有効にする方法

概要

zsh の場合はを紹介します

環境

  • macOS 11.7.10
  • google-cloud-sdk 468.0.0
  • Python 3.12.2

補完を有効にする方法

  • vim ~/.zshrc
source "$(brew --prefix)/share/google-cloud-sdk/path.zsh.inc"
source "$(brew --prefix)/share/google-cloud-sdk/completion.zsh.inc"

参考

brew info google-cloud-sdk を実行するとちゃんと表示されていました
bash などの方法も記載されています

brew info google-cloud-sdk
==> google-cloud-sdk: 470.0.0 (auto_updates)
https://cloud.google.com/sdk/
/usr/local/Caskroom/google-cloud-sdk/468.0.0 (129B)
From: https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/g/google-cloud-sdk.rb
==> Name
Google Cloud SDK
==> Description
Set of tools to manage resources and applications hosted on Google Cloud
==> Artifacts
google-cloud-sdk/install.sh (Installer)
google-cloud-sdk/bin/git-credential-gcloud.sh -> git-credential-gcloud (Binary)
google-cloud-sdk/completion.zsh.inc -> /usr/local/share/zsh/site-functions/_google_cloud_sdk (Binary)
google-cloud-sdk/bin/gsutil (Binary)
google-cloud-sdk/bin/anthoscli (Binary)
google-cloud-sdk/bin/bq (Binary)
google-cloud-sdk/completion.bash.inc -> /usr/local/etc/bash_completion.d/google-cloud-sdk (Binary)
google-cloud-sdk/bin/docker-credential-gcloud (Binary)
google-cloud-sdk/bin/gcloud (Binary)
==> Caveats
To add gcloud components to your PATH, add this to your profile:

  for bash users
    source "$(brew --prefix)/share/google-cloud-sdk/path.bash.inc"

  for zsh users
    source "$(brew --prefix)/share/google-cloud-sdk/path.zsh.inc"
    source "$(brew --prefix)/share/google-cloud-sdk/completion.zsh.inc"

  for fish users
    source "$(brew --prefix)/share/google-cloud-sdk/path.fish.inc"

==> Analytics
install: 11,433 (30 days), 32,208 (90 days), 107,295 (365 days)

最後に

これでサブコマンドなどの補完が行えます

2024年5月1日水曜日

docker kics 超入門

docker kics 超入門

概要

kicsはIaC用のセキュリティチェックツールです
Dockerfile や k8s の YAML ファイルにセキュリティ的な脆弱性や記載がないかをチェックしてくれます

今回はとりあえず動かしてみました
docker で動かします

環境

  • macOS 11.7.10
  • docker 24.0.2
  • kics 1.7.13

コマンド

  • docker run -t -v $(pwd):/path checkmarx/kics:latest scan -p /path -o "/path/"

結果確認

ターミナルにも結果が表示されますが results.json というファイルでも保存されています

  • cat results.json
{
        "kics_version": "v1.7.13",
        "files_scanned": 2,
        "lines_scanned": 10,
        "files_parsed": 2,
        "lines_parsed": 10,
        "lines_ignored": 0,
        "files_failed_to_scan": 0,
        "queries_total": 291,
        "queries_failed_to_execute": 0,
        "queries_failed_to_compute_similarity_id": 0,
        "scan_id": "console",
        "severity_counters": {
                "HIGH": 0,
                "INFO": 0,
                "LOW": 0,
                "MEDIUM": 0,
                "TRACE": 0
        },
        "total_counter": 0,
        "total_bom_resources": 0,
        "start": "2024-04-17T01:07:50.40506235Z",
        "end": "2024-04-17T01:09:50.037978163Z",
        "paths": [
                "/path"
        ],
        "queries": []
}

再帰的に実行するには

オプションではないようです
-p オプションで複数のディレクトリを指定すれば複数のディレクトリに対してスキャンしてくれるようです

  • docker run -t -v $(pwd):/path checkmarx/kics:latest scan -p /path -p /path/archive -o "/path/"

脆弱性がある場合には

例えば Dockerfile に脆弱性がある場合は以下のようなエラーが表示されます

Missing User Instruction, Severity: HIGH, Results: 1
Description: A user should be specified in the dockerfile, otherwise the image will run as root
Platform: Dockerfile
CWE: 250
Learn more about this vulnerability: https://docs.kics.io/latest/queries/dockerfile-queries/fd54f200-402c-4333-a5a4-36ef6709af2f

        [1]: ../../path/Dockerfile:1

                001: FROM ruby:3.1.2
                002: 
                003: ADD . /home



Results Summary:
HIGH: 1
MEDIUM: 5
LOW: 4
INFO: 0
TOTAL: 10

Generating Reports: Done

最後に

IaC のコードのセキュリティチェックができる kics を試してみました
使うこと自体は簡単にできそうです

Query という機能を使って簡単に脆弱性ルールを追加することもできます

参考サイト

2024年4月30日火曜日

Traefik 超入門

Traefik 超入門

概要

Traefik は Host ヘッダベースのリバースプロキシです
ルーティングのルールを細かく設定できたりパスベースのルーティングも実現できます
設定ファイルも YAML で記載できる他正規表現などのルールも作成できます
今回は docker 上で簡単に動かしてみました

環境

  • macOS 11.7.10
  • docker 24.0.2
  • traefik 3.0

まずは traefik を起動する

リバースプロキシのみ起動します
このあとでアプリをぶら下げて Host ヘッダベースでバランシングできているか確認します

  • vim docker-compose.yml
version: '3'

services:
  reverse-proxy:
    image: traefik:v3.0
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  • docker-compose up -d

で localhost:8080 にアクセスすると traefik のダッシュボードを確認できます

この段階で localhost にアクセスしてもアプリがないので 404 が返ってきます

アプリをぶら下げる

traefik の下にアプリをぶら下げる設定を追加します

  • vim docker-compose.yml
version: '3'

services:
  reverse-proxy:
    image: traefik:v3.0
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  whoami:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"

コンテナにラベルを振ることで Host ヘッダの値に応じてバランシングしてくれます
一度 down させて再度起動します
ちなみに traefik/whoami はただのデバッグ用のアプリになります

  • docker-compose down
  • docker-compose up -d
  • docker-compose ps
NAME                         IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
python-try-reverse-proxy-1   traefik:v3.0        "/entrypoint.sh --ap…"   reverse-proxy       25 seconds ago      Up 23 seconds       0.0.0.0:80->80/tcp, 0.0.0.0:8080->8080/tcp
python-try-whoami-1          traefik/whoami      "/whoami"                whoami              25 seconds ago      Up 23 seconds       80/tcp

これで今度は Host ヘッダを付与してアプリにアクセスしてみます
するとちゃんと whoami アプリにアクセスできていることが確認できます

  • curl -H "Host: whoami.docker.localhost" localhost
Hostname: 716c5a018b13
IP: 127.0.0.1
IP: 172.19.0.2
RemoteAddr: 172.19.0.3:57016
GET / HTTP/1.1
Host: whoami.docker.localhost
User-Agent: curl/8.4.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.19.0.1
X-Forwarded-Host: whoami.docker.localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: c8479f4b7c06
X-Real-Ip: 172.19.0.1

もう一台追加してバランシングされるか確認する

一台だと味気ないのでもう一台追加してちゃんと Host を変えたときに正しいアプリにバランシングされることを確認してみます
whoami アプリをもう一つ追加します

  • vim docker-compose.yml
version: '3'

services:
  reverse-proxy:
    image: traefik:v3.0
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  whoami:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
  whoami2:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    labels:
      - "traefik.http.routers.whoami2.rule=Host(`whoami2.docker.localhost`)"

ラベルも whoami2 にして追加します
これで再作成してみます

  • docker-compose down
  • docker-compose up -d
  • docker-compose ps
NAME                         IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
python-try-reverse-proxy-1   traefik:v3.0        "/entrypoint.sh --ap…"   reverse-proxy       55 seconds ago      Up 54 seconds       0.0.0.0:80->80/tcp, 0.0.0.0:8080->8080/tcp
python-try-whoami-1          traefik/whoami      "/whoami"                whoami              55 seconds ago      Up 54 seconds       80/tcp
python-try-whoami2-1         traefik/whoami      "/whoami"                whoami2             55 seconds ago      Up 54 seconds       80/tcp

そして再度 Host ヘッダを変更してアクセスすると別の whoami アプリにバランシングされていることが確認できます

  • curl -H "Host: whoami.docker.localhost" localhost
Hostname: 77c5ff6e72f4
IP: 127.0.0.1
IP: 172.21.0.4
RemoteAddr: 172.21.0.3:36418
GET / HTTP/1.1
Host: whoami.docker.localhost
User-Agent: curl/8.4.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.21.0.1
X-Forwarded-Host: whoami.docker.localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 60057b9154e0
X-Real-Ip: 172.21.0.1
  • curl -H "Host: whoami2.docker.localhost" localhost
Hostname: e6cef80986c8
IP: 127.0.0.1
IP: 172.21.0.2
RemoteAddr: 172.21.0.3:47060
GET / HTTP/1.1
Host: whoami2.docker.localhost
User-Agent: curl/8.4.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.21.0.1
X-Forwarded-Host: whoami2.docker.localhost
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 60057b9154e0
X-Real-Ip: 172.21.0.1

最後に

Traefik を使って Host ベースのアプリバランシングをしてみました
nginx などのリバースプロキシでも Host ベースのバランシングはできますがラベルにルール付やデフォルトで管理用のダッシュボードなどは用意されていません
また定義を YAML で書けるのも便利です
違いについてはこのあたりが非常にまとまっているので興味があれば見てみてください

また今回は紹介しませんが k8s との連携も簡単にできるのも特徴です

参考サイト

2024年4月29日月曜日

Outlook のマクロでメッセージの一覧を取得する方法

Outlook のマクロでメッセージの一覧を取得する方法

概要

メッセージの件数が長いと Outlook が固まるので注意しましょう

環境

  • Windows 10
  • Outlook 2302 build 16.0

サンプルコード

Option Explicit
Public Sub Outlook_mail_list()
    ' 変数定義
    Dim InboxFolder, i, n, k As Long
    Dim outlookObj As Outlook.Application
    Dim myNameSpace, objmailItem As Object
 
    ' 変数初期化
    Set outlookObj = CreateObject("Outlook.Application")
    Set myNameSpace = outlookObj.GetNamespace("MAPI")
    Set InboxFolder = myNameSpace.GetDefaultFolder(6)
    n = 2
 
    ' 現在のメールの数を取得、数が多い場合はループの数は固定したほうが良い
    Debug.Print InboxFolder.Items.Count
    ' For i = 1 To InboxFolder.Items.Count
    For i = 1 To 100
        Set objmailItem = InboxFolder.Items(i)
 
        ' メールの内容を表示、参照可能なプロパティはこちらを参照 https://learn.microsoft.com/ja-jp/office/vba/api/outlook.mailitem.senderemailaddress
        Debug.Print i
        Debug.Print "SenderEmailAddress: " + objmailItem.SenderEmailAddress
        Debug.Print "To" + objmailItem.To
        ' Stop
        n = n + 1
    Next
 
    ' 変数の削除
    Set outlookObj = Nothing
    Set myNameSpace = Nothing
    Set InboxFolder = Nothing
End Sub

最後に

マクロでメッセージの一覧を取得して見ました
エクセルのマクロでも動作するはずです

参考サイト

2024年4月28日日曜日

Apps Script で doGet を実装して http リクエストを受け取れるようにする

Apps Script で doGet を実装して http リクエストを受け取れるようにする

概要

Google Apps Script にはウェブアプリとしてデプロイできる機能がありこれを使うことで Apps Script の特定の関数を curl からコールできるようになります
今回はその方法を紹介します

環境

  • Google Apps Script (2024/04/22 時点)

doGet の実装

function doGet(e) {
  var params = JSON.stringify(e);
  return ContentService.createTextOutput(params).setMimeType(ContentService.MimeType.JSON);
}

アプリのデプロイ

curl で実行する場合には全公開する必要があります

ウェブアプリの URL が発行されるのでメモしておきます

動作確認

リダイレクトがあるので curl の場合は -L を付与します

  • curl -L "https://script.google.com/macros/s/xxxxxx/exec"
{"contextPath":"","parameter":{},"parameters":{},"contentLength":-1,"queryString":""}

クエリストリングも送れます

  • curl -L "https://script.google.com/macros/s/xxxxxx/exec?name=hawksnowlog"
{"contentLength":-1,"parameters":{"name":["hawksnowlog"]},"queryString":"name=hawksnowlog","parameter":{"name":"hawksnowlog"},"contextPath":""}

最後に

AppsScript で定義した関数を外部からコールできるようにしてみました
ショートカットにしておけばアクセスするだけで特定のデータを抽出したりできます

自分しかアクセスできないウェブアプリの場合はブラウザでしかアクセスできないので注意しましょう

参考サイト

2024年4月26日金曜日

RQ のジョブのタイムアウトについて調査した

RQ のジョブのタイムアウトについて調査した

概要

RQ のジョブにはタイムアウトが設定されておりデフォルトでは 180 秒のようです
このタイムアウトは単純にワーカーが実行しているジョブが 180 秒経過しても終了しない場合には強制的にジョブを終了させる設定になります

環境

  • macOS 11.7.10
  • Python 3.11.6
  • rq 1.16.1

挙動を確認するためのスクリプト

ジョブは 190 秒待つようにします
初回はエンキュー時にタイムアウトなしでエンキューして本当にタイムアウトが発生するか確認します

  • vim lib/utils.py
import time


class Message:
    def say(self, **kwargs):
        print("Start say")
        print(kwargs)
        time.sleep(190)
        print("End say")
  • vim app.py
from datetime import timedelta

from redis import Redis
from rq import Queue
from rq_scheduler import Scheduler

from lib.util import Message

queue = Queue("default", connection=Redis(host="192.168.1.2"))
scheduler = Scheduler(queue=queue, connection=queue.connection)


scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello")
# scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello", timeout=3000)

実行

  • pipenv run rq worker
  • pipenv run rqscheduler
  • pipenv run app.py

ログ

実際に実行するとジョブの終了時に表示されるメッセージが表示されずに 180 経過してジョブが矯正終了していることがわかります
なお JobTimeoutException で終了したジョブはリトライなどはされませんでした

08:39:14 default: say(msg='hello') (c97f39f8-c197-43ac-828f-245d78ef78f0)
Start say
{'msg': 'hello'}
08:42:14 [Job c97f39f8-c197-43ac-828f-245d78ef78f0]: exception raised while executing (say)
Traceback (most recent call last):
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/worker.py", line 1431, in perform_job
    rv = job.perform()
         ^^^^^^^^^^^^^
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/job.py", line 1280, in perform
    self._result = self._execute()
                   ^^^^^^^^^^^^^^^
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/job.py", line 1317, in _execute
    result = self.func(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/data/repo/python-try/lib/util.py", line 8, in say
    time.sleep(190)
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/timeouts.py", line 63, in handle_death_penalty
    raise self._exception('Task exceeded maximum timeout value ({0} seconds)'.format(self._timeout))
rq.timeouts.JobTimeoutException: Task exceeded maximum timeout value (180 seconds)

timeout=3000 にして再度試す

コメントにしていた部分を変更して再度エンキューしてみます
今度はジョブのタイムアウトを 3000 秒にしてエンキューします

すると先ほどタイムアウトしたジョブがタイムアウトせずにちゃんと最後まで実行されることが確認できました

08:52:14 default: say(msg='hello') (91aab157-25a5-44b9-898b-4da6dcb3baff)
Start say
{'msg': 'hello'}
End say
08:55:24 default: Job OK (91aab157-25a5-44b9-898b-4da6dcb3baff)
08:55:24 Result is kept for 500 seconds

最後に

ジョブの実行時間が長くなりそうな場合にはエンキュー時に timeout を設定してあげましょう

参考サイト

2024年4月25日木曜日

RQ のシグナルハンドリングについて調べてみる

RQ のシグナルハンドリングについて調べてみる

概要

RQ でプロセスハンドリングの挙動を確認してみました

環境

  • macOS 11.7.10
  • Python 3.11.6
  • rq 1.16.1

サンプルコード

  • vim app.py
from datetime import timedelta

from redis import Redis
from rq import Queue
from rq_scheduler import Scheduler

from lib.util import Message

queue = Queue("default", connection=Redis(host="192.168.1.2"))
scheduler = Scheduler(queue=queue, connection=queue.connection)


scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello")
  • vim lib/util.py
import time


class Message:
    def say(self, **kwargs):
        print("Start say")
        time.sleep(30)
        print(kwargs)
  • pipenv run rq worker
  • pipenv run rqscheduler
  • pipenv run python app.py

10秒以内にワーカーを Ctrl+c で停止した場合

ワーカーがジョブを掴んでからワーカーのプロセスを停止しようとするとちゃんと最後まで処理が終了してからワーカーが停止しました

11:21:53 default: say(msg='hello') (4d5a4db9-e8c9-4ee3-b64c-1dd266e66068)
Start say
11:21:55 Worker 98f9aac6f73445f685d4f5588abd514f [PID 32100]: warm shut down requested
{'msg': 'hello'}
11:22:03 default: Job OK (4d5a4db9-e8c9-4ee3-b64c-1dd266e66068)
11:22:03 Result is kept for 500 seconds
11:22:03 Worker rq:worker:98f9aac6f73445f685d4f5588abd514f: stopping on request
11:22:03 Unsubscribing from channel rq:pubsub:98f9aac6f73445f685d4f5588abd514f

kill -9 した場合

ワーカーがジョブを実行中に kill -9 を送信してみました
その場合ワーカーは Warm shut down せずにすぐに終了してしまいます

12:05:54 default: say(msg='hello') (b4eac96d-9591-482c-9753-e3a9c150d83b)
Start say
zsh: killed     pipenv run rq worker

redis 外部からアクセスできない場合

Protection mode が有効になっているので一旦無効にしましょう

  • CONFIG SET protected-mode no

docker の場合

docker stop はデフォルトでは SIGTERM -> SIGKILL の順番で信号を送ります
SIGKILL までの時間は time というオプションで変更可能でデフォルトで10秒になります

ワーカーを docker 上で動かし docker stop した際のワーカーの挙動を確認します

  • vim Dockerfile
FROM python:3.11.9-bullseye

COPY . /home
WORKDIR /home

RUN pip install pipenv
RUN pipenv install

CMD ["pipenv", "run", "rq", "worker", "--url", "redis://192.168.1.2:6379"]
  • docker build -t worker .
  • docker create --name worker worker
  • docker start worker
  • docker logs -f worker

でワーカーを起動します
スケジューラとエンキューは python から行います

  • pipenv run rqscheduler
  • pipenv run python app.py

これでワーカーがジョブを実行中にワーカーコンテナを停止します

  • docker stop worker
03:28:54 default: say(msg='hello') (76dbdc1b-5af2-4fa1-b4d2-49793454b235)
03:28:57 Worker 15c934ce3084457da63ade0e89ea14b6 [PID 1]: warm shut down requested

SIGTERM を受取り Warm shut down がスタートしますが10秒後に docker が強制的に SIGKILL を送信するためワーカーはジョブの完了を待たずに終了してしました

また強制終了したジョブは FailedJobRegistry に移動するようです

StartedJobRegistry cleanup: Moving job to FailedJobRegistry (due to AbandonedJobError)

time=60 にしてみる

SITTERM -> SIGKILL の送信にかかる時間を 60 秒に伸ばしてみます

  • docker stop --time=60 worker

すると今度はちゃんとワーカーがジョブの終了を待ってから停止していることが確認できました

03:32:54 default: say(msg='hello') (8e04f278-c5b1-4c4a-9069-3105960129f4)
03:33:14 Worker faa70ceee374410ba9416f55d74f4927 [PID 1]: warm shut down requested
Start say
{'msg': 'hello'}
03:33:25 default: Job OK (8e04f278-c5b1-4c4a-9069-3105960129f4)
03:33:25 Result is kept for 500 seconds
03:33:25 Worker rq:worker:faa70ceee374410ba9416f55d74f4927: stopping on request
03:33:25 Unsubscribing from channel rq:pubsub:faa70ceee374410ba9416f55d74f4927

最後に

RQ のシグナルハンドリングについて調べてみました
基本は RQ 側で実装されているので気にすることはないですが docker などと組み合わせる場合には SIGKILL の送信タイミングについて考慮する必要がありそうです