2025年12月4日木曜日

VSCode で Remote tunnel する

VSCode で Remote tunnel する

概要

操作するファイルがリモートサーバにある場合に vscode.dev という中継サーバを介してローカルで起動している VSCode で編集することができます

環境

  • Ubuntu 24.04
    • code 1.106.3
  • Windows 11
    • VSCode 1.106.3

リモートサーバ側でトンネル作成

  • curl -Lk 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64' --output vscode_cli.tar.gz
  • tar -xf vscode_cli.tar.gz

これで code というバイナリが入手できます
あとはホームディレクトリに起動して code を起動します

  • cd
  • ./code tunnel

Github Account か Microsoft Account を選択する必要があるので Github Account を選択します

起動すると例のごとくhttps://github.com/login/device にアクセスしてトークンを取得して ./code tunnel に入力してあげます

これでトンネル用の URL が発行されるのでこれをブラウザに入力してもいいのですがそれだと code-server と変わらないので VSCode からアクセスしてみます

VSCode の Remote - Tunnels 拡張をインストールする

  • 左メニューの拡張を選択
  • Remote tunnel を入力して検索
  • 「Remote - Tunnels」拡張をインストール
  • 左メニューに Remote - Tunnels 拡張のアイコンが追加されるので選択
  • さきほど ./code tunnel で起動したサーバが一覧にあるのでそれを選択
  • 初回はブラウザで OAuth の認証が発生するので ./code tunnel と同じ Github Account でログインします
  • 接続に成功すると VSCode が再起動しサーバ上のファイルが操作できるようになります

その他

  • ローカルモードに切り替える場合は切断する必要があるので左下の接続先サーバを選択し「Close Remote Connection」を選択します
  • ./code tunnel 時にはトークンのみですが VSCode 側でトンネルと VSCode を紐づける場合にはブラウザの OAuth が必要になるので VSCode が起動する環境にブラウザがありかつそのブラウザから該当の Github Account でログインできる必要があるので注意しましょう

最後に

普通はこんな使い方しないのでどうしてもローカルで開発できない場合などに使うんだと思います

もしくは Powershell などを使いたくない場合なども使うのかなと思います

参考サイト

2025年11月22日土曜日

Speckit 超入門

Speckit 超入門

概要

code-server + speckit を試してみました
一応アプリは作成してくれましたが生成されるファイルが微妙に違うのでまだサポートしていないのかもしれません

環境

  • Ubuntu 24.04
  • speckit (specify 0.0.22)
  • code-server v4.105.1

インストール

  • uv tool install specify-cli --from git+https://github.com/github/spec-kit.git

初期化

  • specify init speckit_test
    • copilot を選択
    • sh を選択

code-server を開く

  • speckit_test を vscode で開く
  • モデルを Claude Sonnet 4.5 にする

以下 code-server の Github copilot chat のチャット欄で作業します

規則作成

  • /speckit.constitution コード品質、テスト標準、ユーザーエクスペリエンスの一貫性、パフォーマンス要件に焦点を当てた原則を作成します。

/home/devops/work/speckit_test/.github/prompts/speckit.constitution.prompt.md が作成された

仕様作成

  • /speckit.specify 家庭や業務用キッチンで使用できるシンプルで視認性の高いタイマーを、Web ブラウザー経由で提供します。1分、3分、5分を設定するためのボタンを配置します。ボタンを押すと、その時間からカウントダウンが開始されます。カウントダウン中は、残り時間を目立つように表示します。カウントダウン中に再度ボタンを押すと、タイマーがリセットされ、新たなカウントダウンが開始されます。

/home/devops/work/speckit_test/.github/prompts/speckit.specify.prompt.md が作成されました

実装計画作成

  • /speckit.plan HTML5、JavaScript(ES6)、CSS3 で作成してください

/home/devops/work/speckit_test/.github/prompts/speckit.plan.prompt.md が作成されました

作業タスクの作成

  • /speckit.tasks

/home/devops/work/speckit_test/.github/prompts/speckit.tasks.prompt.md が作成されました

実装

  • /speckit.implement

/home/devops/work/speckit_test/.github/prompts/speckit.implement.prompt.md が作成されました

また実際に作成されたコードは以下の通りです

ls -ltrR kitchen-timer/
kitchen-timer/:
total 24
drwxr-xr-x 3 devops docker 4096 Nov 21 13:23 assets
-rw-r--r-- 1 devops docker 1844 Nov 21 13:24 index.html
drwxr-xr-x 2 devops docker 4096 Nov 21 13:24 css
drwxr-xr-x 2 devops docker 4096 Nov 21 13:26 js
-rw-r--r-- 1 devops docker 7522 Nov 21 13:26 README.md

kitchen-timer/assets:
total 4
drwxr-xr-x 2 devops docker 4096 Nov 21 13:23 sounds

kitchen-timer/assets/sounds:
total 0

kitchen-timer/css:
total 8
-rw-r--r-- 1 devops docker 6334 Nov 21 13:24 styles.css

kitchen-timer/js:
total 16
-rw-r--r-- 1 devops docker 2975 Nov 21 13:25 timer.js
-rw-r--r-- 1 devops docker 3650 Nov 21 13:25 audio.js
-rw-r--r-- 1 devops docker 6150 Nov 21 13:26 app.js

動作確認

  • cd speckit_test/kitchen-timer
  • python -m http.server 8000
  • curl localhost:8000

最後に

code-server で speckit を動かしてみました
冒頭にも紹介しましたが挙動が微妙でまだ code-server ではサポートしていないのかもしれません

本当は git コマンドで branch も作成してくれるよういなのですがやってくれませんでした

またモデルが GPT-4.1 だと動きませんでした

続けて修正、エンハンスなどしたい場合は /speckit.specify から再度行います

参考サイト

2025年11月21日金曜日

GCP で CPU 監視する方法

GCP で CPU 監視する方法

概要

コンソールでの手順を紹介します

環境

  • GCP (2025/11/21 時点)

手順

  1. Compute Engine のページを開く
  2. 「VM インスタンス」を選択
  3. 監視したい VM を選択
  4. 「オブザーバビリティ」を選択
  5. 右上にある「推奨アラート」を選択
  6. VM Instance - High CPU Utilization (web) を選択 -> 作成
  7. 「通知チャンネルを使用」で通知したいチャネルを選択 -> 作成

確認

モニタリングにアラートポリシーがあることを確認しましょう

最後に

GCP の VM で CPU 監視する方法を紹介しました
コンソール作業なので UI が変わった場合は手順が変わるので注意してください

しきい値を変えたい場合はポリシーを直接変更すれば OK です

Slack の通知チャネルを作成する方法は以下の参考リンクを参照してください

参考サイト

2025年11月11日火曜日

Ubuntu に uv をインストールする方法

Ubuntu に uv をインストールする方法

概要

グローバルな環境にインストールする方法を紹介します
基本は mcp などに使う場合にこの方法でインストールします

環境

  • Ubuntu 24.04
  • uv 0.9.8

コマンド

curl -LsSf https://astral.sh/uv/install.sh | sh

ログ

downloading uv 0.9.8 x86_64-unknown-linux-gnu
no checksums to verify
installing to /home/devops/.local/bin
  uv
  uvx
everything's installed!

インストールされるパス

ホームディレクトリの .local/bin 配下なので注意してください
PATH が設定されていない場合は PATH に追加しましょう

アップグレードする方法

再度インストールするための curl を叩けば OK です

最後に

pip を使ってインストールする場合はパスや管理方法が変わるので注意しましょう

参考サイト

2025年11月6日木曜日

code-server では Python の拡張だけでは import などを自動でしてくれない

code-server では Python の拡張だけでは import などを自動でしてくれない

概要

VSCode では Pylance の languageServer が使えるのですが code-server では使えません
その影響でメソッドやモジュールの自動インポートはしてくれません

今回はそんな場合の対処方法を紹介します

環境

  • macOS 15.7.1
  • code-server 4.105.1

インストール

  • brew install code-server

起動

  • brew services run code-server

Python 拡張のインストール

  • 左メニュー拡張
  • 検索バーに「Python」
  • ms-python 製の Python 拡張をインストールします

BasedPyright のインストール

  • 左メニュー拡張
  • 検索バーに「basedpyright」
  • detachhead 製の BasedPyright 拡張をインストールします

設定

  • settings.json
{
    "workbench.colorTheme": "Default Dark Modern",
     "basedpyright.analysis.diagnosticMode": "openFilesOnly"
}

動作確認

これでメソッド名の保管時に自動で import してくれるようになります
また型チェックなどもしてくれますが純正の pyright とは違うので注意してください

もし pyright 基準にしたい場合は以下の設定を入れると多少は近くなります

{
    "basedpyright.analysis.typeCheckingMode": "standard"
}

最後に

VSCode では普通にできても code-server ではできないことが多くあるので注意しましょう

参考サイト

2025年11月4日火曜日

OpenSpec 超入門

OpenSpec 超入門

概要

OpenSpec は簡単に言えば Markdown (仕様書) を作成するだけでアプリが開発できてしまうツールです
今までは特にフォーマットもなくプロンプトに命令していましたがそれを定型化したツールになります
今回は簡単なブラウザのタイマーアプリを OpenSpec を使って作ってみました

環境

  • Ubuntu 24.04
  • node 22.21.0
  • openspec 0.13.0
  • code-server v4.104.3

インストール

  • npm install -g @fission-ai/openspec@latest

初期化

まずはプロジェクトを初期化します

  • openspec init

 ????   ?????   ??????  ??  ??   ?????  ?????   ??????   ?????
??  ??  ??  ??  ??      ??? ??  ??      ??  ??  ??      ??
??  ??  ?????   ?????   ?? ???   ????   ?????   ?????   ??
??  ??  ??      ??      ??  ??      ??  ??      ??      ??
 ????   ??      ??????  ??  ??  ?????   ??      ??????   ?????

Welcome to OpenSpec!

Step 1/3

Configure your OpenSpec tooling
Let's get your AI assistants connected so they understand OpenSpec.

Press Enter to continue.

次に使用するLLMを選択します

Step 2/3

Which natively supported AI tools do you use?
Use ↑/↓ to move ・ Space to toggle ・ Enter selects highlighted tool and reviews.

    Natively supported providers (? OpenSpec custom slash commands available)
? ○ Auggie (Augment CLI)
  ○ Claude Code
  ○ Cline
  ○ CodeBuddy Code (CLI)
  ○ Crush
  ○ Cursor
  ○ Factory Droid
  ○ OpenCode
  ○ Kilo Code
  ○ Windsurf
  ○ Codex
  ○ GitHub Copilot
  ○ Amazon Q Developer

    Other tools (use Universal AGENTS.md for Amp, VS Code, GitHub Copilot, …)
  ○ Universal AGENTS.md (always available)

Selected configuration:
  - No natively supported providers selected

今回は Github Copilot を使います

Step 3/3

Review selections
Press Enter to confirm or Backspace to adjust.

? GitHub Copilot

これで初期化が完了です

? GitHub Copilot
? OpenSpec structure created
? AI tools configured

? OpenSpec initialized successfully!

Tool summary:
? Root AGENTS.md stub created for other assistants
? Created: GitHub Copilot
? Skipped: Auggie, Claude Code, Cline, CodeBuddy Code, Crush, Cursor, Factory Droid, OpenCode, Kilo Code, Windsurf, Codex, and Amazon Q Developer

Use `openspec update` to refresh shared OpenSpec instructions in the future.

Next steps - Copy these prompts to GitHub Copilot:
────────────────────────────────────────────────────────────
1. Populate your project context:
   "Please read openspec/project.md and help me fill it out
    with details about my project, tech stack, and conventions"

2. Create your first change proposal:
   "I want to add [YOUR FEATURE HERE]. Please create an
    OpenSpec change proposal for this feature"

3. Learn the OpenSpec workflow:
   "Please explain the OpenSpec workflow from openspec/AGENTS.md
    and how I should work with you on this project"
────────────────────────────────────────────────────────────

project.md
の編集

ここにどんなアプリを作成したいか概要を記載ます とりあえず参考サイトにもあるブラウザで動かくタイマアプリを作成してみます Project Conventions の欄はとりあえず不要なので削除しています

  • vim openspec/project.md
# Project Context

## Purpose
家庭や業務用キッチンで使用できるシンプルで視認性の高いタイマーを、Web ブラウザー経由で提供します。

- 1分、3分、5分を設定するためのボタンを配置します。ボタンを押すと、その時間からカウントダウンが開始されます。
- カウントダウン中は、残り時間を目立つように表示します。
- カウントダウン中に再度ボタンを押すと、タイマーがリセットされ、新たなカウントダウンが開始されます。

## Tech Stack
- HTML5
- JavaScript(ES6)
- CSS3

提案書の作成

code-server の Github Copilot のチャット欄に /openspec-proposal コマンドを入力します

  • /openspec-proposal UIを作成する

すると必要な Markdown ファイルを changes 配下に作成してくれます

ls -lR openspec/changes/ui-timer/
openspec/changes/ui-timer/:
total 12
-rw-r--r-- 1 devops docker  374 Nov  4 13:57 proposal.md
drwxr-xr-x 3 devops docker 4096 Nov  4 13:57 specs
-rw-r--r-- 1 devops docker  341 Nov  4 13:57 tasks.md

openspec/changes/ui-timer/specs:
total 4
drwxr-xr-x 2 devops docker 4096 Nov  4 13:57 timer-ui

openspec/changes/ui-timer/specs/timer-ui:
total 4
-rw-r--r-- 1 devops docker 810 Nov  4 13:57 spec.md

作成されたファイルの中身を確認して仕様に間違いがないか確認しましょう 変更は Copilot Chat にお願いしてもいいですし手動で書き換えても OK です 問題なければ Keep してファイルを保存しましょう

検証する

チャット欄に「検証して」と入力すると勝手に openspec validate コマンドを実行してくれます 内部的には openspec validate ui-timer --strict を実行しています

生成された Markdown に不備があると以下のようにエラーが表示されます ちなみにエラーがよくわからなくても「修正してください」と命令し再度「検証してください」と命令すればそれだけで修正できます

code-server が openspec コマンドを実行できない場合は PATH を確認するか PATH に通っているけど実行できない場合は sudo systemctl restart code-server@USER で再起動しましょう

コードを生成する

あとは /openspec-apply を Copilot Chat に入力すればコードを生成してくれます 各種 html や js が作成されたのが確認できます 内容を確認し問題なければ Keep しましょう

動作確認

これもチャットに「動作確認して」と依頼すればブラウザでアプリを開いて動作確認してくれます

Web アプリがない場合は Python で簡易用のアプリも起動してくれます

アーカイブする

アプリの挙動が問題なければアーカイブします チャット欄に /openspec-archive を入力しましょう すると生成されたコードは archive 配下に移動します

以後アプリに修正を入れたい場合は再度提案を作成し修正するようにしましょう そうすることで changes/archive 配下に日付ごとにディレクトリが作成され提案ごとに履歴管理することができるようになります

最後に

OpenSpec に入門してみました 基本は Markdown を書くだけで開発していく感じです 今まではプロンプトに自分なりの質問やテンプレートを使って質問していたのが完全に形式化したのが OpenSpec かなと思います

新規系は簡単に導入できそうですが既存のアプリにこれを導入するとなるとかなり大変そうです

参考サイト

2025年11月3日月曜日

GCP の Ops エージェントを完全にアンインストールする方法

GCP の Ops エージェントを完全にアンインストールする方法

概要

コンソールから Ops エージェントをインストールするとなぜかアンインストールしても勝手に再インストールしてしまうので完全にアンインストールする方法を紹介します

環境

  • Ops エージェント 2.60.0
  • Ubuntu 22.04

まずは purge

  • sudo apt purge google-cloud-ops-agent

設定ファイルの削除

purge した場合は基本ないはずですが一応確認します

  • sudo ./add-google-cloud-ops-agent-repo.sh --remove-repo
  • sudo rm /etc/apt/sources.list.d/google-cloud-monitoring.list

/opt 配下にも何も無いことを確認します

VM のカスタムメタデータの削除

これがあるとエージェントが勝手に再インストールしている可能性があるので削除します
コンソールでも OK です

  • gcloud compute instances remove-metadata YOUR_INSTANCE_NAME --zone=YOUR_ZONE --keys=enable-osconfig

VM のラベルの削除

  • VM インスタンス -> 選択 -> 編集 -> ラベルを管理 -> goog-ops-agent-policy のラベルを削除

デフォルト

以下のエージェントだけになっていればデフォルトの状態です

dpkg -l | grep google
ii  google-compute-engine            20230808.00-0ubuntu1~22.04.1            all          Google Compute Engine guest environment.
ii  google-compute-engine-oslogin    20231004.00-0ubuntu1~22.04.3            amd64        Google Compute Engine OS Login
ii  google-guest-agent               20231004.02-0ubuntu1~22.04.5            amd64        Google Compute Engine Guest Agent
ii  google-osconfig-agent            20240524.03-0ubuntu2~22.04.1            amd64        Google OS Config Agent

動作確認

最終的にはコンソールでオブザーバビリティを確認したい際に「Opsエージェントをインストール」が表示されていれば完全に削除できています
インストール中の保留中やメトリクスが送信できないような文言の場合はまだタグやラベルが残っている可能性があります

最後に

GCP の小さめのインスタンスに Ops エージェントをインストールすると CPU とメモリを食い尽くして他のプロセスが全く動かなくなるので安易にインストールするのはやめておきましょう

2025年11月1日土曜日

Gitlab CI で nikto を動かし DAST 的なことをする

Gitlab CI で nikto を動かし DAST 的なことをする

概要

前回 Gitlab CI のサービスを使って Web アプリを起動しそのアプリにテストする方法を紹介しました
今回は nikto を実行してみます

環境

  • Gitlab 18.2.6
  • Gitlab Runner 18.2.2
  • Python 3.12.11

.gitlab-ci.yml

stages:
  - build   # ビルドステージ
  - test    # テストステージ
  - pentest # ペネトレーションテストステージ

build:
  stage: build
  image:
    name: moby/buildkit:rootless  # BuildKit を使用するための Docker イメージ
    entrypoint: [""]  # デフォルトのエントリーポイントを無効化
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox  # BuildKit の設定
  before_script:
    # Docker 認証情報を設定
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    # BuildKit を使用して Docker イメージをビルドし、GitLab Container Registry にプッシュ
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

app:
  stage: test
  services:
    # ビルドした Docker イメージをサービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスが起動するまで待機
    - echo "Waiting for the service to be ready..."
    - >
      for i in {1..30}; do
        curl -s http://testapp:5000 && break || sleep 1;  # サービスが応答するまで最大 30 秒間リトライ
      done
    - echo "Service is ready."  # サービスが起動したことを確認

validate:
  stage: test
  image: curlimages/curl:latest  # curl を使用するための軽量イメージ
  services:
    # ビルドした Docker イメージを再度サービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスに対してレスポンスを検証
    - echo "Running validation tests..."
    - |
      RESPONSE=$(curl -s http://testapp:5000)  # サービスにリクエストを送信しレスポンスを取得
      if [ "$RESPONSE" = "Hello, World!" ]; then
        echo "Validation passed!"  # レスポンスが期待通りの場合
      else
        echo "Validation failed: Expected 'Hello, World!' but got '$RESPONSE'"  # レスポンスが期待と異なる場合
        exit 1  # ジョブを失敗させる
      fi

pentest:
  stage: pentest
  image:
    name: ghcr.io/sullo/nikto:latest
    entrypoint: [""]
  services:
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp
  script:
    - echo "Running Nikto penetration test..."
    - mkdir -p reports  # レポートを保存するディレクトリを作成
    - nikto.pl -h http://testapp:5000 -F htm -o reports/report.html
    - echo "Nikto scan completed. Report saved to reports/report.html."
  artifacts:
    paths:
      - reports/report.html  # レポートをアーティファクトとして保存
    expire_in: 1 week  # レポートの保存期間を 1 週間に設定

最後に

Gitlab Ultimate に登録していない場合に DAST が使えないので自分で .gitlab-ci.yml にペネトレーションテスト的なことを記載する必要があります
またレポートも自分で保存する必要があるので artifact を使って保存するようにしましょう

2025年10月31日金曜日

Gitlab CI で service を使って Web アプリを起動し簡単なテストを行う

Gitlab CI で service を使って Web アプリを起動し簡単なテストを行う

概要

イメージをビルドしビルドしたイメージからアプリを起動しそのアプリに対してテストするみたいな流れを GItlab CI でやってみます

今回 Web アプリは Python を使っていますが好きな言語のアプリで OK です

環境

  • Gitlab 18.2.6
  • Gitlab Runner 18.2.2
  • Python 3.12.11

Pipfile

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "*"

[dev-packages]

[requires]
python_version = "3.12"

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

Dockerfile

FROM python:3.12.11-slim-bookworm

# 作業ディレクトリを設定
WORKDIR /app

# 必要なファイルをコンテナにコピー
COPY Pipfile Pipfile
COPY Pipfile.lock Pipfile.lock
COPY app.py app.py

# 必要なPythonパッケージをインストール
RUN pip install pipenv
RUN pipenv install

# アプリケーションを起動
CMD ["pipenv", "run", "python", "app.py"]

.gitlab-ci.yml

stages:
  - build  # ビルドステージ
  - test   # テストステージ

build:
  stage: build
  image:
    name: moby/buildkit:rootless  # BuildKit を使用するための Docker イメージ
    entrypoint: [""]  # デフォルトのエントリーポイントを無効化
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox  # BuildKit の設定
  before_script:
    # Docker 認証情報を設定
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    # BuildKit を使用して Docker イメージをビルドし、GitLab Container Registry にプッシュ
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

app:
  stage: test
  services:
    # ビルドした Docker イメージをサービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスが起動するまで待機
    - echo "Waiting for the service to be ready..."
    - >
      for i in {1..30}; do
        curl -s http://testapp:5000 && break || sleep 1;  # サービスが応答するまで最大 30 秒間リトライ
      done
    - echo "Service is ready."  # サービスが起動したことを確認

validate:
  stage: test
  image: curlimages/curl:latest  # curl を使用するための軽量イメージ
  services:
    # ビルドした Docker イメージを再度サービスとして起動
    - name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      alias: testapp  # サービスに "testapp" というホスト名を割り当て
  script:
    # サービスに対してレスポンスを検証
    - echo "Running validation tests..."
    - |
      RESPONSE=$(curl -s http://testapp:5000)  # サービスにリクエストを送信しレスポンスを取得
      if [ "$RESPONSE" = "Hello, World!" ]; then
        echo "Validation passed!"  # レスポンスが期待通りの場合
      else
        echo "Validation failed: Expected 'Hello, World!' but got '$RESPONSE'"  # レスポンスが期待と異なる場合
        exit 1  # ジョブを失敗させる
      fi

最後に

Gitlab には Ultimate ライセンスを使っていると DAST を使えるのですがそうじゃないライセンスだと使えません
そんな場合に自分で DAST 的なことをする必要があるので今回のような流れで実装できます

次回は nikto を組み合わせてセキュリティテストしてみます

2025年10月26日日曜日

LetsEncrypt で 0001 や 0002 などの証明書が取得できてしまった場合の解決方法

LetsEncrypt で 0001 や 0002 などの証明書が取得できてしまった場合の解決方法

概要

前の設定が残っている状態で新規に証明書を発行すると連番が付いてしまいます
こうなると /etc/letsencrypt/live/ 配下で証明書を管理するディレクトリにも 0001 などが付き色々と面倒なので削除する方法を紹介します

なお過去に紹介した docker compose バージョンの certbot を使います

環境

  • Ubuntu 22.04
  • certbot 5.1.0

削除する

サフィックスが付いている証明書および付いていないやつも削除しましょう

  • docker compose run --rm certbot delete --cert-name your-domain.com-0001
  • docker compose run --rm certbot delete --cert-name your-domain.com-0002
  • docker compose run --rm certbot delete --cert-name your-domain.com

これで再度取得すればサフィックスなし版が取得できます
なおサフィックス版があるかないかは certificates サブコマンドを使って確認しましょう

  • docker compose run --rm certbot certificates

最後に

ホスト側の証明書をコンテナにマウントして使う場合にサフィックスがあるといろいろ面倒なのでなし版で取得するようにしましょう

参考サイト

2025年10月25日土曜日

Microsoft Graph API を使って個人の OneDrive を操作する方法

Microsoft Graph API を使って個人の OneDrive を操作する方法

概要

前回 OneDrive にアクセスできるトークンの取得まで行いました
今回はそのトークンを使って実際に OneDrive にアクセスします

環境

  • macOS 15.7.1
  • Python 3.12.11

サンプルコード

import json
import os

import requests

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"
REDIRECT_URI = "http://localhost:8080"
TOKEN_FILE = "onedrive_token.json"
GRAPH_API_BASE = "https://graph.microsoft.com/v1.0"


# =========================
# トークン管理
# =========================
def load_tokens():
    """保存済みトークンを読み込む"""
    if not os.path.exists(TOKEN_FILE):
        raise FileNotFoundError(f"{TOKEN_FILE} が存在しません。先にトークン取得を実行してください。")
    with open(TOKEN_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def save_tokens(tokens):
    """トークンを保存"""
    with open(TOKEN_FILE, "w", encoding="utf-8") as f:
        json.dump(tokens, f, indent=2)


def refresh_access_token(refresh_token, client_id, client_secret, redirect_uri):
    """リフレッシュトークンからアクセストークンを更新"""
    token_url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
    data = {
        "client_id": client_id,
        "client_secret": client_secret,
        "redirect_uri": redirect_uri,
        "grant_type": "refresh_token",
        "refresh_token": refresh_token
    }
    res = requests.post(token_url, data=data)
    res.raise_for_status()
    return res.json()


# =========================
# OneDrive 操作
# =========================
def get_headers(access_token):
    return {"Authorization": f"Bearer {access_token}"}


def upload_file(access_token, local_path, remote_name):
    """小さなファイルをアップロード"""
    url = f"{GRAPH_API_BASE}/me/drive/root:/{remote_name}:/content"
    with open(local_path, "rb") as f:
        res = requests.put(url, headers=get_headers(access_token), data=f)
    if res.status_code in (200, 201):
        print(f"✅ アップロード成功: {remote_name}")
    else:
        print(f"❌ アップロード失敗 ({res.status_code}): {res.text}")


def list_files(access_token):
    """ルートディレクトリのファイル一覧取得"""
    url = f"{GRAPH_API_BASE}/me/drive/root/children"
    res = requests.get(url, headers=get_headers(access_token))
    if res.status_code == 200:
        items = res.json().get("value", [])
        print("📄 OneDrive ファイル一覧:")
        for item in items:
            print(f" - {item['name']}")
    else:
        print(f"❌ 取得失敗 ({res.status_code}): {res.text}")


def delete_file(access_token, remote_name):
    """ファイル削除"""
    url = f"{GRAPH_API_BASE}/me/drive/root:/{remote_name}"
    res = requests.delete(url, headers=get_headers(access_token))
    if res.status_code in (204, 200):
        print(f"🗑️ 削除成功: {remote_name}")
    else:
        print(f"❌ 削除失敗 ({res.status_code}): {res.text}")


# =========================
# メイン
# =========================
if __name__ == "__main__":
    tokens = load_tokens()
    access_token = tokens["access_token"]

    # 必要に応じてリフレッシュ
    tokens = refresh_access_token(tokens["refresh_token"], CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
    access_token = tokens["access_token"]
    save_tokens(tokens)

    # サンプルファイル作成
    sample_file = "test.txt"
    with open(sample_file, "w", encoding="utf-8") as f:
        f.write("Hello OneDrive via Graph API!")

    # アップロード
    upload_file(access_token, sample_file, "test.txt")

    # ファイル一覧
    list_files(access_token)

    # 削除(必要であればコメント解除)
    delete_file(access_token, "test.txt")

ポイント

  • トークンが切れた場合にリフレッシュトークを使って再度トークンを取得します
  • 毎回行っても問題ないので実際は毎回リフレッシュトークを使って問い合わせたほうがいいです
  • トークンをファイルで保存していますが漏洩すると大変なので管理には注意しましょう

最後に

Python で OneDrive の操作をしてみました
認証が面倒ですが一度認証してしまえばあとはスクリプトを実行するだけです

アプリのシークレットに期限があるのでその期限がすぎたら再度ブラウザを開いてトークンファイルを更新する必要があります

2025年10月24日金曜日

Microsoft Graph API を使って個人の OneDrive にアクセスする方法

Microsoft Graph API を使って個人の OneDrive にアクセスする方法

概要

前回 Graph API に入門しました
今回は個人用の OneDrive アクセスしてみます
なそアプリーケーション権限は使えず「委任されたアクセス許可」に変更する必要があるので注意してください

簡単に言うと API をコールするためのアクセストークンを一度ブラウザを開いて取得しなければなりません

環境

  • macOS 15.7.1
  • Python 3.12.11

サポートされているアカウントの種類

  • 任意の組織ディレクトリ内のアカウント (任意の Microsoft Entra ID テナント - マルチテナント) と個人用の Microsoft アカウント (Skype、Xbox など)

もしくは

  • 個人用 Microsoft アカウントのみ

権限の設定

  • 管理 -> API のアクセス許可 -> アクセス許可の追加 -> Microsoft Graph -> 委任されたアクセス許可
    • Files.ReadWrite.All
    • Sites.ReadWrite.All
    • User.Read
    • offline_access
  • 既定のディレクトリに管理者の権限を与えますを選択

コールバックURLの設定

  • 管理 -> 認証 -> URLの追加 -> http://localhost:8080

トークンの取得

これは一度だけ実行します
表示された URL にブラウザでアクセスしログインします

  • vim ./fetch_token.py
import http.server
import json
import socketserver
import urllib.parse
import webbrowser

import requests

# ======== 設定 ========
TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"
REDIRECT_URI = "http://localhost:8080"
SCOPES = ["Files.ReadWrite.All", "Sites.ReadWrite.All", "User.Read", "offline_access"]
TOKEN_FILE = "onedrive_token.json"
# =======================


def start_local_server():
    """localhostで認可コードを受け取るための簡易HTTPサーバー"""
    class Handler(http.server.SimpleHTTPRequestHandler):
        def do_GET(self):
            query = urllib.parse.urlparse(self.path).query
            params = urllib.parse.parse_qs(query)
            if "code" in params:
                setattr(self.server, "auth_code", params["code"][0])
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b"Authorization successful. You can close this window.")
            else:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Missing authorization code.")

    with socketserver.TCPServer(("localhost", 8080), Handler) as httpd:
        setattr(httpd, "auth_code", None)
        print("🌐 ローカルサーバー起動中 (http://localhost:8080)")
        httpd.handle_request()
        return getattr(httpd, "auth_code", None)


def get_tokens(auth_code):
    """認可コードからトークンを取得"""
    token_url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
    data = {
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "client_secret": CLIENT_SECRET,
        "code": auth_code,
        "grant_type": "authorization_code"
    }
    res = requests.post(token_url, data=data)
    res.raise_for_status()
    return res.json()


def save_tokens(tokens):
    """トークンをローカルに保存"""
    with open(TOKEN_FILE, "w", encoding="utf-8") as f:
        json.dump(tokens, f, indent=2)
    print(f"💾 トークンを保存しました → {TOKEN_FILE}")


def main():
    # 1️⃣ 認可URLを生成
    params = {
        "client_id": CLIENT_ID,
        "response_type": "code",
        "redirect_uri": REDIRECT_URI,
        "response_mode": "query",
        "scope": " ".join(SCOPES)
    }
    auth_url = f"https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?{urllib.parse.urlencode(params)}"

    print("\n🔗 以下のURLを開いてMicrosoftアカウントでサインインしてください:")
    print(auth_url)
    webbrowser.open(auth_url)

    # 2️⃣ 認可コード受け取り
    auth_code = start_local_server()
    if not auth_code:
        print("❌ 認可コードを取得できませんでした。")
        return

    print(f"✅ 認可コード取得成功: {auth_code[:20]}...")

    # 3️⃣ トークン取得
    tokens = get_tokens(auth_code)
    print("✅ アクセストークン・リフレッシュトークン取得成功")
    save_tokens(tokens)

    print("\n🧾 トークン内容:")
    print(json.dumps(tokens, indent=2))


if __name__ == "__main__":
    main()

動作確認

  • pipenv run python ./app.py

でブラウザが開いたらMicrosoft アカウントでログインしアプリに許可を与えます
成功するとトークン情報が onedrive_token.json に保存されるので今後はこれを使って OneDrive にアクセスします

最後に

Python から OneDrive にアクセスするためのトークンの取得まで行いました
次回は実際にファイルのアップロードや取得などを行います

Onedrive for bussiness だとアプリケーションの許可の権限で操作できるようです

2025年10月23日木曜日

Microsoft Graph API を Python で使ってみる

Microsoft Graph API を Python で使ってみる

概要

Microsoft365 の API である Microsoft Graph API を Python から利用してみました

環境

  • macOS 15.7.1
  • Python 3.12.11

アプリの作成

  • https://portal.azure.com にログイン
  • 左メニューから「Microsoft Entra ID」を選択
  • 「アプリの登録」->「新規登録」をクリック
    • 名前 -> 何でも OK
    • サポートされているアカウントの種類 -> この組織ディレクトリのみに含まれるアカウント (既定のディレクトリ のみ - シングル テナント)
    • リダイレクト URI -> とりあえず空でOK
  • テナントID、クライアントID をコピーしておく

シークレットの登録

  • 「証明書とシークレット」->「新しいクライアント シークレット」から発行
  • 「値」をコピーしておく

API アクセスの許可

  • アプリ登録ページ左メニューから「API のアクセス許可」を選択
  • 「+ アクセス許可の追加」->「Microsoft Graph」->「アプリケーションの許可」を選択
    • Sites.ReadWrite.All
    • Files.ReadWrite.All
  • アクセス許可の追加」->「管理者の同意を与える」をクリック(管理者権限が必要)

ライブラリインストール

  • pipenv install msal requests

アクセストークンを取得するサンプルコード

とりあえず作成したアプリがアクセスできる認証情報だけ取得してみます

  • vim ./app.py
import pprint

from msal import ConfidentialClientApplication

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"

app = ConfidentialClientApplication(
    CLIENT_ID,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    client_credential=CLIENT_SECRET,
)

result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])

pprint.pprint(result)
  • pipenv run python ./app.py

以下のようにトークンが取得できれば OK です

{'access_token': 'xxx',
 'expires_in': 3599,
 'ext_expires_in': 3599,
 'token_source': 'identity_provider',
 'token_type': 'Bearer'}

sharepoint のサイト一覧を取得する

  • vim ./app.py
import requests
from msal import ConfidentialClientApplication

TENANT_ID = "xxx"
CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"

app = ConfidentialClientApplication(
    CLIENT_ID,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    client_credential=CLIENT_SECRET,
)
token = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
if token is None:
    raise Exception("トークンの取得に失敗しました。")
headers = {"Authorization": f"Bearer {token['access_token']}"}

# サイト一覧を検索(すべて表示)
url = "https://graph.microsoft.com/v1.0/sites?search=*"
response = requests.get(url, headers=headers)
print(response.json())
sites = response.json()

for s in sites.get("value", []):
    print(f"名前: {s['name']}")
    print(f"URL:  {s['webUrl']}")
    print(f"ID:   {s['id']}")
    print("---")

sharepoint は当然ですがライセンスがないと使えないので「Tenant does not have a SPO license.」のエラーになります

最後に

Python から MicrosoftGraphAPIをコールしてみました
無料アカウントでもここまではいけます
次回は実際に Microsoft365 上にあるリソースにアクセスしてみます

参考サイト

2025年10月22日水曜日

journal ログをすべて削除する方法

journal ログをすべて削除する方法

概要

忘れるのでメモ

環境

  • Ubuntu 22.04

全削除

sudo du -sh /var/log/
3.3G    /var/log/
  • sudo journalctl --vacuum-time=1s

削除ログ

Vacuuming done, freed 0B of archived journals from /run/log/journal.
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-000000000279f7c3-00063ffa5ec9cd0c.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027b400b-00064004588b93b5.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027d6843-000640152308d284.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000027f9176-00064025f94a8e93.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000281b863-00064036e00660b8.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000283e0cf-00064047f2873c97.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028609f5-00064058f8e381d0.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028834bc-0006406a1746446c.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028a5e91-0006407b42809cd3.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028c873b-0006408c87b6d808.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-00000000028e2463-0006409965aaafa8.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000028eb3bd-0006409dca5e037c.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000290dc16-000640ae8e9c7381.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-0000000002912098-000640b0a2d36d3b.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002930478-000640bf5eb5c522.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/user-1002@cb5cf46ac4024d6ab8ac0f7039ee62f0-0000000002936af1-000640c27aa7db32.journal (8.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002952cde-000640d027fee751.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-000000000297559b-000640e0f3a72cf5.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002997e37-000640f1d327fe5d.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029ba58e-00064102c1378bd0.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029dce0e-00064113bde6de0a.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-00000000029ff7c5-00064124cc9e6f43.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a2215b-00064135ea1f4111.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a44b4b-000641471cc25b72.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a6736b-00064158657fee95.journal (128.0M).
Deleted archived journal /var/log/journal/95b2c59cdf7701096ea7d3386e92e124/system@6e6c307cc5634e74888ce601b9437fcf-0000000002a89d70-00064169c011da77.journal (128.0M).
Vacuuming done, freed 2.7G of archived journals from /var/log/journal/95b2c59cdf7701096ea7d3386e92e124.
Vacuuming done, freed 0B of archived journals from /var/log/journal.

sudo du -sh /var/log/
443M    /var/log/

微妙に残ります
インスタンス自体を再起動すると更に減ります

du 以外にも journalctl 専用のコマンドでログの使用量を確認することもできます

  • sudo journalctl --disk-usage
Archived and active journals take up 136.0M in the file system.

そもそも保存させる容量を調整する

  • sudo vim /etc/systemd/journald.conf
[Journal]
SystemMaxUse=100M
  • sudo systemctl restart systemd-journald

ちなみに SystemMaxUse のデフォルトはディスクサイズの10%なので30GBのディスクであれば3.0GBほど保存します

最後に

だいたい dockerd のログでうまります

参考サイト

2025年10月21日火曜日

buildah 超入門

buildah 超入門

概要

buildah を使ってみました
基本は docker のように使えますが rootless 実行するのがかなり面倒そうです

環境

  • Ubuntu 24.04
  • buildah 1.33.7
  • docker 28.5.1

インストール

  • sudo apt -y update
  • sudo apt -y install buildah

apt の公式リポジトリからインストールできますが最新版はインストールできません

イメージのビルド (rootモード)

  • sudo buildah build -f Dockerfile -t lab .

sudo 付きは root モードなのであまりお勧めできません

イメージのビルド (rootless モード)

不明

Error during unshare(CLONE_NEWUSER): Permission denied
ERRO[0000] parsing PID "": strconv.Atoi: parsing "": invalid syntax
ERRO[0000] (Unable to determine exit status)

になり実行できませんでした unshare --user --map-root-user --mount あたりが実行できないと rootless 実行は厳しいです

確認

  • sudo buildah images
REPOSITORY                 TAG                     IMAGE ID       CREATED          SIZE
localhost/lab              latest                  6b467d5adf55   31 seconds ago   129 MB
docker.io/library/python   3.12.11-slim-bookworm   688a685f6a1f   2 months ago     129 MB

イメージのプッシュ

  • sudo buildah login your-container-registry-address
  • sudo buildah tag localhost/lab your-container-registry-address/username/reponame
  • sudo buildah push your-container-registry-address/username/reponame

最後に

基本は docker コマンドと同様に使えます
rootless 実行が良い点なのですが rootless 実行できるようにするまでがかなり大変そうなのでそこが何ともといった感じです

当然ですが docker デーモンがなくても buildah を使ってビルドはできるのでそれだけでも kaniko の代用にはなりそうです

  • sudo systemctl stop docker
  • sudo systemctl stop docker.socket

参考サイト

2025年10月20日月曜日

GitlabCI で buildkit を使って docker イメージをビルドする

GitlabCI で buildkit を使って docker イメージをビルドする

概要

buildkit 単体で動かそうとするとデーモンが必要だったり runc が必要だったりするのですが GitlabCI で公式のイメージを使用すると簡単に使えたのでメモしておきます

環境

  • buildkit
  • Gitlab 18.2.6
  • Gitlab Runner 18.2.2

.gitlab-ci.yml

.gitlab-ci.yml と同じディレクトリに Dockerfile があれば以下でそのまま使えます
他に環境変数などの設定が必要な場合は variables などで設定してください

stages:
  - test

build:
  stage: test
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true

最後に

kaniko からの代替としては一番近い感じはします
以下にもありますが公式にも kaniko からの移行ドキュメントがあります

参考サイト