サイオステクノロジー(Tech.Lab)のブログ - TECH PLAY

TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

全739件

こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 Apple は、MacOS 上で仮想マシンを使って Linux コンテナを作成・実行するためのツール「container」を、オープンソースとして公開しました。 Apple⁠⁠、macOS上でLinuxコンテナを直接実行できる「container」をオープンソースとして公開 https://gihyo.jp/article/2025/06/apple-container アメリカの大手保険会社 Aflac は、国内ネットワーク上で不審なアクセスを検知したと発表しました。 米 アフラック、サイバー攻撃で個人情報漏洩の可能性 https://rocket-boys.co.jp/security-measures-lab/aflac-us-announces-possible-data-breach-after-cyberattack/ 2025/6/20、Microsoft は「Windows Subsystem for Linux」v2.6.0 をプレリリース版として公開しました。オープンソースとしては初のリリースです。 「Windows Subsystem for Linux」に初のオープンソースリリース/「WSL 2.6.0」がプレリリース版として公開 https://www.msn.com/ja-jp/news/techandscience/windows-subsystem-for-linux-%E3%81%AB%E5%88%9D%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%82%BD%E3%83%BC%E3%82%B9%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9-wsl-260-%E3%81%8C%E3%83%97%E3%83%AC%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E7%89%88%E3%81%A8%E3%81%97%E3%81%A6%E5%85%AC%E9%96%8B/ar-AA1HddiV ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年6月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
OSS よろずチームの神﨑です。 RHEL 10 同梱版の httpd の変更点についてドキュメントと疎通確認したのでまとめていきたいと思います。 まずは、ドキュメントからです。 httpd に関する変更点 RHEL 公式ドキュメントを確認したところ以下の記述がありました。 RHEL 10.0 同梱版 httpd は Apache HTTP Server 2.4.62 デフォルトでロードされるモジュールの変更 mod_authnz_fcgi がデフォルトで有効化されるようになりました。 セキュリティの強化 httpd.service ユニットファイルのデフォルト設定でセキュリティが強化されるようになりました。 OpenSSL の ENGINE サポート削除 SSLCryptoDevice 設定ディレクティブは使用不可になりました。 Berkeley DB データベースのサポート削除 RHEL 9 以降、Berkeley DB はサポートされなくなりました。 mod_authz_dbm などのモジュールは、デフォルトで LMDB データベースタイプを使用します。SDBM データベースタイプも利用可能です。 RHEL 10 の導入における検討事項 – 第14章インフラストラクチャサービス より 以下で httpd.service と OpenSSL についてもう少し補足していきます。 httpd.service で追加された設定 RHEL 9.6 同梱版 httpd の httpd.service ファイルと比較したところ以下が追加されていました。 /usr/lib/systemd/system/httpd.service より (追加は ハイライト部分 ) [Unit] Description=The Apache HTTP Server Wants=httpd-init.service After=network.target remote-fs.target nss-lookup.target httpd-init.service Documentation=man:httpd.service(8) [Service] Type=notify Environment=LANG=C ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND ExecReload=/usr/sbin/httpd $OPTIONS -k graceful # Send SIGWINCH for graceful stop KillSignal=SIGWINCH #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # [Service] # Environment=OPTIONS=-DMY_DEFINE [Unit] Description=The Apache HTTP Server Wants=httpd-init.service After=network.target remote-fs.target nss-lookup.target httpd-init.service Documentation=man:httpd.service(8) [Service] Type=notify Environment=LANG=C ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND ExecReload=/usr/sbin/httpd $OPTIONS -k graceful # Send SIGWINCH for graceful stop KillSignal=SIGWINCH KillMode=mixed DevicePolicy=closed KeyringMode=private LockPersonality=yes MemoryDenyWriteExecute=yes OOMPolicy=continue PrivateDevices=yes PrivateTmp=true ProtectClock=yes ProtectControlGroups=yes ProtectHome=read-only ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectSystem=yes RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes SystemCallArchitectures=native 追加されたパラメータ(波線以下)の概要は以下の通りです。 先ほど述べた通りセキュリティ強化に関するパラメータが追加されていますね。 DevicePolicy=closed プロセスが /dev 以下のデバイスノードにアクセスするのを制限します。 closed に設定すると、デフォルトではどのデバイスにもアクセスできません。 KeyringMode=private サービスプロセスが自身のプライベートなカーネルキーリングを持つようにします。 LockPersonality=yes personality システムコールを介してプロセスの実行モードを変更することを禁止します。 MemoryDenyWriteExecute=yes プロセスが書き込み可能かつ実行可能 (W+X) なメモリ領域を作成することを禁止します。 PrivateDevices=yes サービスに独自の /dev ツリーを提供し、システム全体の /dev ツリーへのアクセスを制限します。 ProtectClock=yes サービスプロセスがシステムクロックの設定を変更することを禁止します。 ProtectControlGroups=yes サービスプロセスがコントロールグループ (cgroups) のファイルシステムにアクセスすることを禁止します。 ProtectHome=read-only サービスプロセスからユーザーのホームディレクトリ ( /home/ 、 /root/ など) を読み取り専用でマウントします。 ProtectHostname=yes サービスプロセスがシステムのホスト名を変更することを禁止します。 ProtectKernelLogs=yes サービスプロセスがカーネルログ( dmesg などがアクセスする情報)にアクセスしたり、変更したりすることを禁止します。 ProtectKernelModules=yes サービスプロセスがカーネルモジュールをロードしたりアンロードしたりするのを禁止します。 ProtectKernelTunables=yes サービスプロセスがカーネルのsysctlパラメータ ( /proc/sys 以下の設定) を変更することを禁止します。 ProtectSystem=yes /usr , /boot , /etc などのシステムディレクトリを読み取り専用でマウントします。これにより、サービスがこれらの重要なシステムファイルを変更したり、新たなファイルを書き込んだりするのを防ぎます。 RestrictNamespaces=yes サービスプロセスが新しい名前空間を作成したり、既存の名前空間に参加したりするのを制限します。 RestrictRealtime=yes サービスプロセスがリアルタイムスケジューリングポリシー ( SCHED_FIFO , SCHED_RR ) を使用することを禁止します。 RestrictSUIDSGID=yes サービスプロセスが setuid または setgid フラグを持つファイルを実行することを禁止します。 SystemCallArchitectures=native サービスプロセスが、ネイティブアーキテクチャのシステムコールのみを使用できるように制限します。 詳細は man 5 systemd.exec よりご確認ください。 httpd に関連する OpenSSL の変更 OpenSSL 3.0 の導入により、従来の Engine が Provider へと置き換えられた結果、 openssl-pkcs11 エンジンが削除され、代わりに pkcs11-provider が提供されています。 参考 以下の公式ドキュメントを参考にしました。 RHEL 10 の導入における検討事項 10.0 リリースノート ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post RHEL 10 同梱版の httpd 変更点まとめ 1 ~ドキュメント編 first appeared on SIOS Tech. Lab .
はじめに  2025年6月16日から二日間開催されたKubeCon + CloudNativeCon Japan( https://events.linuxfoundation.org/kubecon-cloudnativecon-japan/ )に参加しました。 第一回目のせいなのか、ところどころである休憩時間では通路を埋めるほど人が集まり、非常に盛況なイベントでした。  場所は、お台場にある、ヒルトン東京お台場と呼ばれる場所で行っており、その1Fを使用してスポンサーブースや技術セッションを聞いたりしました。実際のセッション内容は後日公開されたり、セッションで使用した資料は公式サイトに公開してあるものもあります。 ゆりかもめの台場駅から歩いてすぐに会場がある。    基本的に、発表内容は英語のセッションですが各セッションルームにQRコードがかかれた看板があり、そのQRコードのリンクからセッション内容がチャットのように流れていたため英語が理解できなくて内容自体はすぐに確認可能など技術の進歩に驚きます。 参加者の証明であるバッチを受け取り、自分自身へ話しかけていいかどのように話しかけるかのシール セッションの翻訳内容を聞くためのリンクが書かれたQR 翻訳アプリの内容について 一日目について  参加者はまず、受付でバッチと呼ばれる作業あるため受付でかなり長い行列ができていました。そのためキーノートセッション開始前に来ると受付のためキーノートの会場入りに間に合わない人も数人いたことや、開始の10分前にはメイン会場が入れなくなるので、余裕をもって受付に参加したほうが参加しやすいです。 一日目でバッチ受け取りの受付は8時から実施したが、Tシャツの配布は混雑解消のためキーノートが終了する10:45から配布するなど時間をずらしていた    一日目は、Prometheus 3.0のセッションや2-node Kubernetesのセッションを聞いてきました。  Prometheus 3.0のセッションでは、Prometheusのこれからについて、OpenTelemetryとの強化について触れており、OpentElemetryについてより重要だと認識しました。    2-node Kubernetesセッションでは、複数のノードを使用して司令塔であるコントロールプレーンを作成する内容であり、通常では3ノードを使用して作成する所を2ノードで作成する内容でした。 おもな特徴として、2ノードで作成できるのですが、etcdと呼ばれるデータストアの保全性を維持するため、sambaやnfsの共有ディスクを使用してデータの正当性を維持しております。用意するハードの数が減るため管理するPCが減る利点もあるため、その技術には将来性があると考えます。   二日目について  一日目が大盛況なこともあり、Tシャツの配布ブースでは男性用のTシャツのサイズが二種類しか存在しないことがありました。また、入場者数は一日目と大きな違いがなく二日目も大盛況であり、お昼に提供する一般の弁当がなくなったり、休憩で提供させている部屋を埋めつくすこともありました。 二日目のセッションでは、Green OpenTelemetryやThe Future of Prometheus Exposition Format、From Moon Prism Power To eBPF Super Saiyanというセッションを聞いてきました。   Green OpenTelemetryでは、環境問題をテーマにOpenTelemetryを使用して消費電力の最適化を行うために、Kubernetes based Efficient Power Level Exporter(通称Kepler)を使用して、eBPFから取得できるCPU使用率からエネルギー消費量に変化させて少ないエネルギーで運用し環境問題改善に貢献しようという話でした。 The Future of Prometheus Exposition FormatではOpenMetricsと呼ばれるメトリックログを出力する際の標準化フォーマットの歴史やこれからへのOpenMetrics 2.0へむけて向けての改善を話す内容で、ここでもOpentelemetryというキーワードが出ており可観測の注目が非常に高いと感じました。 スポンサーブースについて   スポンサーブースでは、Google Cloudのブースで2025年の1月にオープンソースで公開されたソフトのKubernetes History Inspectorと呼ばれるソフトが展示されており、Kubernetesの監査ログから過去の変更点が視覚的に追いながら調査できることが便利だと感じました。 また、ZOZOさんのブースでは、特定のサーバへの複数アクセスのテストでKubernetes上で立てたPodからアクセスするようなソフトのでも実施しており、この点もKubernetesの利点だとも再認識しました。 さらに、CNCFプロジェクトのステッカー配布ブースがあることが少し感動しました。 まとめ  日本でやる第一回のKubeConであったため非常に大盛況のイベントありました。基本的に英語のセッションでしたが、翻訳アプリのおかげで内容の理解に問題を感じないため言語の壁は技術の進化によって減ってきたと感じました。  本イベントでますますOpentelemetoryなどのAI学習に与えるデータを作ることも重要性も感じるようなイベントだと考える機会でした ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post KubeCon + CloudNativeCon Japan 2025 参加レポート first appeared on SIOS Tech. Lab .
初めに ども!最近は人間とチャットするよりもAIとチャットすることが増えている龍ちゃんです。AIサービスの使い方が多岐にわたってきて、そのトピックだけでブログが執筆できそうなくらいです。 今回はAIとあまり関係ない、認証のお話です。内容としては「Azure Static Web Appsで組み込み認証とユーザー情報の取り扱い」となります。認証プロバイダーとしてGoogleを使用しています。 構築がしたことがない方は、「 GoogleによるSSOを持つAzure Static Web Appsのアプリを作成する 」の記事を参考に構築して試してみてください。 前提技術 Azure Static Web Apps(以降 SWA)をStandardプランで運用して、カスタム認証としてGoogleを設定していることが前提となります。カスタム認証はStandardプランから使用することができ、Freeプランで今回と同様の検証することができません。また、カスタム認証を有効化すると、他の認証プロバイダーが提供する組み込みプロバイダー( /.auth/login/github …など)が利用できなくなります。 SWA CLIを使用すれば、認証フローをローカルでエミュレートすることができます。 Next.js+DevContainer環境でSWA CLIをセットアップしているブログがありますので、こちらを参考に構築するのをお勧めします 。 バックエンドは、 将来的にSWAとAPI連携を活用して接続をする予定 になります。Azure FunctionsやAzure App Serviceにデプロイされるのが想定されます。ローカルでの検証もできるので、任意の環境で大丈夫です。 私の検証環境では、nest.jsを使用して構築しています。コードとしては、TypeScriptになりますが概念としては他の言語でも通じることですので参考にしてみてください。 今回検証する内容としては以下になります。 staticwebapp.config.json のロールベースルーティング: 参考リンク API連携をした際に x-ms-client-principal を介してユーザー情報を渡す: 参考リンク staticwebapp.config.json を用いてロールベースのルーティングを定義する こちらはプレビューの機能となります。本番環境で利用する場合は、検討して採用を決めてください。 staticwebapp.config.jsonの設定を追加することで、認証時にロールの判定ルートを追加することができます。制限事項として、SWA CLIではロール判定のルートを含めてエミュレートするため、実際の動きはAzure上にデプロイしてからのみ確認することができます。 認証からロール判定の流れは以下の流れになります。 設定項目としては、 auth > rolesSource になります。こちらで設定したエンドポイントに認証後にPOSTリクエストが送られます。今回は /api/assingRoles と設定しています。 //staticwebapp.config.json { "auth": { "rolesSource": "/api/assignRoles", "identityProviders": { "google": { "registration": { "clientIdSettingName": "GOOGLE_CLIENT_ID", "clientSecretSettingName": "GOOGLE_PROVIDER_AUTHENTICATION_SECRET" } } } }, "routes": [ { "route": "/admin/", "allowedRoles": ["admin"] }, { "route": "/", "allowedRoles": ["authenticated"] } ], "responseOverrides": { "404": { "rewrite": "/404/index.html", "statusCode": 404 }, "401": { "statusCode": 302, "redirect": "/.auth/login/google" } } } POSTリクエスト付帯されるボディ情報としては以下になります。こちらは、Azure EntraIDを設定した場合のサンプルになります。こちらの情報をもとに、ロールの判定を返答することで、ユーザーにカスタムロールをAPI経由で設定することができます。 // 仮のJSON { "identityProvider": "aad", "userId": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee", "userDetails": "ellen@contoso.com", "claims": [ { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "val": "ellen@contoso.com" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", "val": "Contoso" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", "val": "Ellen" }, { "typ": "name", "val": "Ellen Contoso" }, { "typ": "http://schemas.microsoft.com/identity/claims/objectidentifier", "val": "7da753ff-1c8e-4b5e-affe-d89e5a57fe2f" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "val": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" }, { "typ": "http://schemas.microsoft.com/identity/claims/tenantid", "val": "3856f5f5-4bae-464a-9044-b72dc2dcde26" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "val": "ellen@contoso.com" }, { "typ": "ver", "val": "1.0" } ] } それでは、nest.jsで設定方法について見ていきます。エンドポイントから返す値としては、以下のフォーマットに合わせる必要があります。 { roles: string[] } ロール判定の結果、空配列を返答した場合でもSWA側で anonymous ・ authenticated が自動で割り振られます。 import { Body, Controller, Get, Post } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} // https://learn.microsoft.com/ja-jp/azure/static-web-apps/authentication-custom?tabs=google%2Cfunction @Post('/api/assignRoles') async postHello(@Body() req: { userId: string; userDetails: string }): Promise<{ roles: string[] }> { // proiderがgoogleの場合はuserDetailsはメールアドレス情報になっている // 不安な場合は/.auth/meのエンドポイントを確認 // 受け取った情報をもとにrolesを割り当てて返信する // 何もrolesがない場合はから配列を返答する // 検証のためメールアドレスによる判定を行う const roles = await this.appService.assignRoles(req.userDetails); return { roles: roles }; } } 動作確認としては、SWA側で /.auth/me にアクセスすることで確認することができます。ローカルでは、ロール判定も含めてエミュレートするため、エミュレート認証情報が表示されています。 こちらの画像上 Users roles に文字列で設定することで再現することができます。 ロールの設定が完了すれば、 staticwebapp.config.json で allowedRoles で設定することで特定のロールのみが見れるページを制御することが可能になります。 バックエンドでの認証ユーザーの情報にどのようにアクセスするのか? 先ほどのロールベースでの確認で /.auth/me というエンドポイントを使用しました。フロントエンドからGETリクエストを投げることで、認証情報を取得することができます。 技術としてはフロントから自分のIDを取得・指定してAPIアクセスができます。これは、ユーザIDを改ざんしてAPIアクセスすることができてしまうため非推奨です。 SWAからAPI連携しているバックへの認証情報の受け渡しは、ヘッダーに自動挿入されている x-ms-client-principal を活用して行います 。こちらは、Base64でエンコードされた情報として贈られるためでコードをすることで、バックエンドで認証情報を取得することができます。デコードされて受け取ることができる値は以下になります。 type clientPrincipal = { userId: string; // ユーザーID userRoles: string[]; // ユーザーロール identityProvider: string; // 認証プロバイダー userDetails: string; // ユーザー情報 Googleが認証プロバイダーの場合はメールアドレス }; それでは、実装に入っていきます。実装としてはnest.jsのGuardを使用してデコードとリクエストに積み替えを行い、contorllerでは情報を使うだけという実装にしていきます。 まずはGuardを実装します。デコードの方法としては、 公式のサンプル を参考に実装しています。処理としては、以下になります。 ヘッダーから x-ms-client-principal の取得・確認 デコード リクエストに詰め替え import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Observable } from 'rxjs'; import as base64 from 'base-64'; import as utf8 from 'utf8'; type clientPrincipal = { userId: string; userRoles: string[]; identityProvider: string; userDetails: string; }; // このガードは、Azure Static Web Apps (SWA) の認証ヘッダーを検証するために使用されます。 // SWAは、認証されたユーザーの情報を 'x-ms-client-principal' ヘッダーに含めて送信します。 // このガードは、ヘッダーが存在し、正しい形式であることを確認し、 // ユーザー情報をリクエストオブジェクトに追加します。 // 認証されていない場合は、UnauthorizedExceptionをスローします。 // 参考: https://learn.microsoft.com/ja-jp/azure/static-web-apps/user-information?tabs=javascript @Injectable() export class AuthGuardToSwaGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); const clientPrincipalEncoded = request.headers['x-ms-client-principal'] as string; if (!clientPrincipalEncoded) { // 認証情報ヘッダーがない場合、認証されていないとしてスロー throw new UnauthorizedException('X-MS-CLIENT-PRINCIPAL header not found. User is not authenticated.'); } try { // Base64 デコードと UTF-8 デコード const decoded = utf8.decode(base64.decode(clientPrincipalEncoded)); console.log(JSON.parse(decoded)); const clientPrincipal: clientPrincipal = JSON.parse(decoded); request.user = clientPrincipal.userId; request.userRoles = clientPrincipal.userRoles; return true; // 認証成功 } catch (error) { console.error('Failed to decode or parse x-ms-client-principal:', error); // デコードやパースに失敗した場合、不正なヘッダーとしてスロー throw new UnauthorizedException('Invalid X-MS-CLIENT-PRINCIPAL header format.'); } } } Controller実装です。Guardを挿入するとリクエストに積み替えられて取得することができます。 import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { AuthGuardToSwaGuard } from './common/guard/auth-guard-to-swa/auth-guard-to-swa.guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} // SWAの認証情報が必要 @UseGuards(AuthGuardToSwaGuard) @Get('/api/hello') async getHello(@Req() req): Promise<string> { console.log('getHello called with user:', req.user); console.log('getHello called with userRoles:', req.userRoles); return "hello"; } } 確認のため、コンソールに出力しています。あとは、ユーザIDやロールなどの情報をもとに制御することができます。 コラム: staticwebapp.config.json vs. バックエンドAPIでのアクセス制限 SWAでは認証・認可のアプローチとして2つの方法があることがわかりました。設定ファイルでの制御とバックエンドAPIでの制御、これらはどう違うのでしょうか?実際のプロジェクトでどちらを選ぶべきか、迷うところですよね。 実は、この2つのアプローチはそれぞれ異なるタイミングで動作し、得意分野も違います。以下のシーケンス図で、両者の動作の違いを見てみましょう。 比較表:どちらを選ぶべきか? 制御の場所 Azure Static Web Apps (SWA) エッジノード / フロントエンド バックエンドAPIアプリケーション内 処理のタイミング APIへのリクエストがSWAに到達した直後(API実行前) APIがリクエストを受け取り、処理を開始した後 実装方法 設定ファイル (staticwebapp.config.json) の記述 プログラミング言語(TypeScript/NestJS)、ガード、デコレーター、ミドルウェア、ロジックコード 制御の粒度 粗い(URLパスベースの許可/拒否) 細かい(特定のエンドポイント、メソッド、データのフィールド、ビジネスロジック) 主なユースケース – エンドポイント全体へのアクセス制限 – 特定のAPIグループへのアクセス制限 – 不要なバックエンドAPI呼び出しの防止 – データレベルのアクセス制御(例:自分のデータのみ表示) – 情報のマスキング/匿名化 – 特定のアクションの制限(例:更新/削除) – 動的な条件に基づくアクセス制御 – より複雑なビジネスルールに基づく認可 パフォーマンス 高速(APIが呼び出されないため) わずかなオーバーヘッド(APIが呼び出され、処理が実行されるため) 設定の柔軟性 静的(デプロイが必要) 動的(コード変更とデプロイが必要だが、データベースなどと連携すれば実行時にも柔軟な制御が可能) 開発の容易性 シンプル(設定ファイルの記述のみ) 複雑(コードの記述、フレームワークの学習、設計が必要) エラーハンドリング SWAが自動的に401/403を返す カスタムエラーレスポンス(例:NestJSのForbiddenException)を細かく制御可能 推奨される利用 最初の防衛線として、大まかなアクセス制御 詳細なビジネスロジックに基づいた認可、データ処理、複雑なロール要件 使い分けの提案 それぞれの特徴を理解した上で、実際の開発ではどのように使い分けるべきでしょうか? staticwebapp.config.json を使うべき場面 「最初の防衛線」として、粗い粒度(URLパス全体)でのアクセス制限に最適です。バックエンドへの不要なリクエストを防ぐことで、パフォーマンスの向上とセキュリティの強化を同時に実現できます。 管理者専用ページへのアクセス制限 認証済みユーザーのみがアクセス可能なエリアの制御 特定のAPIエンドポイントグループへの一律制限 バックエンドAPIを使うべき場面 「より詳細な制御」が必要な場合に必須となります。データレベル、アクションレベル、または複雑なビジネスロジックに基づいた認可を実現したい場合は、こちらを選択しましょう。情報マスキングやフィルタリングもここで実現できます。 特に、 アクセスするユーザーによってデータが変動する ケースでは、APIベースでの認証が重要になります。 具体例: ブログ記事の閲覧制限 : 一般ユーザーは公開記事のみ、プレミアムユーザーは全記事を閲覧可能にする場合 投稿コンテンツの編集権限 : 投稿されたコメントの編集・削除権限を、投稿者本人と管理者のみに制限する場合 ダウンロード容量制限 : ダウンロード可能なファイルの容量制限を、無料ユーザーは100MB、有料ユーザーは1GBまでとする場合 両方を組み合わせるアプローチ 最も堅牢で柔軟なシステムを構築するには、 両方を組み合わせる ことをお勧めします。 staticwebapp.config.json で大まかなアクセス制御を行い、不要なAPI呼び出しをブロック バックエンドAPIで詳細なビジネスロジックに基づいた認可処理を実装 この多層防御のアプローチにより、パフォーマンスとセキュリティの両方を最適化できます。 まとめ 今回は、Azure Static Web Appsにおける認証とユーザー制御について、フロントエンドとバックエンドの両方のアプローチを詳しく見てきました。特に、x-ms-client-principalを活用した安全なユーザー特定と、きめ細かなロールベース制御の実装方法について解説しました。この知識を活かして、より堅牢なWebアプリケーションの開発に取り組んでいただければ幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure Static Web Apps: x-ms-client-principalで安全なロールベース制御 first appeared on SIOS Tech. Lab .
はじめに こんにちは、サイオステクノロジーの安藤 浩です。 前回の記事では、Ethereum Sepoliaテストネット上のフルノードを構築し、イベントログからERC-721やERC-1155の所有者を特定する方法をご紹介しました。 前回の記事は以下です。 [web3] Ethereum の Sepolia上のイベントログから ERC-721, ERC-1155 の所有者の見つけ方 今回はその続編として、WebSocketを活用したリアルタイムでの所有者の移転が行われた際の監視の方法をお伝えします。 WebSocketを使ったイベント監視の仕組み Ethereumのフルノードは通常HTTPSによるRPC通信を提供していますが、リアルタイム監視にはWebSocketが適しています。WebSocketを使うことで、新しいイベントが発生した時点で即座に通知を受け取ることができます。 前提条件 Sepoliaテストネット上のフルノードがすでに構築されていること 上記に記載の前回の記事でフルノードを構築してください。 [web3] Ethereum の テストネット: Sepolia上での フルノード構築 設定手順 1. Gethの起動設定を変更する まず、GethでWebSocketを有効にするために、起動コマンドに以下のオプションを追加します。 --ws --ws.api eth,net,web3 実際のコマンド例は以下のようになります: gethlocal --sepolia --ws --ws.api eth,net,web3 --http --http.api eth,net,engine,admin --authrpc.jwtsecret 【jwt.hexファイルのパス】 --datadir 【データディレクトリのパス】--syncmode snap 2. WebSocketでイベントをサブスクライブするコード src/sub.ts というファイルを以下の内容で作成します。 import WebSocket from 'ws'; // Geth ノードの WebSocket エンドポイント const wsUrl = 'ws://localhost:8546'; // WebSocket クライアントを作成 const ws = new WebSocket(wsUrl); ws.on('open', () => { console.log('WebSocket connection established.'); const contractAddressList = [ '0xE88Df35e01e3e33Df38FB0B5e324282feCeb20c2', //ERC-721 '0x412E008d6157568F8c621FbF899e7717F0442a94' //ERC-1155 ]; contractAddressList.forEach((contractAddress) => { // eth_subscribe を使用してトランザクションログを監視 const subscriptionRequest = { jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: ['logs', { address: contractAddress, // 特定のコントラクトアドレスを指定 topics: [] // トピックフィルタを指定(空配列はすべてのイベントを受信) } ] }; ws.send(JSON.stringify(subscriptionRequest)); }); }); ws.on('message', (data: any) => { const response = JSON.parse(data.toString()); if (response.method === 'eth_subscription') { console.log('New log:', response.params.result); } else { console.log('Response:', response); } }); ws.on('error', (error: any) => { console.error('WebSocket error:', error); }); ws.on('close', () => { console.log('WebSocket connection closed.'); }); 実行すると、WebSocketの接続が確立され、以下のようなレスポンスが表示されます: [nodemon] starting `ts-node src/sub.ts` WebSocket connection established. Response: { jsonrpc: '2.0', id: 1, result: '0x8debf94ebabaea3a86f403b2e2791a19' } リアルタイム監視の実例 ここでは、実際にNFT関連のトランザクションが発生した際に受信したイベントログをいくつか紹介します。 1. OwnershipTransferredイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x', blockNumber: '0x7a2cce', transactionHash: '0xa8edf869ba4fd375f2fdfc51e557d7a3237299f2583242bb43d839f5930fcf78', transactionIndex: '0x1e', blockHash: '0x57e194d69b6dd85dc431228189c8bf551d0e91a74ed14c6eecfba581c580c73c', logIndex: '0x1a', removed: false } このイベントは、コントラクトのオーナーシップ移転を示しています。Etherscanでは こちら で確認できます。 2. TransferSingleイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', blockNumber: '0x7a2d00', transactionHash: '0xa9f80aced3eb829117ef1cdfaba629a6ae625af9e44febe91bfed5b3dfec3565', transactionIndex: '0x43', blockHash: '0x3823f72b6cf3590915be2b19bc2bff5acbf4f35a772c91e69b3dc4d5804bc7e2', logIndex: '0x43', removed: false } このログはERC-1155のTransferSingleイベントで、単一のトークン移転を記録しています。トランザクションは こちら で確認できます。 New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', blockNumber: '0x7a2d0c', transactionHash: '0xb20b71a0e8b8d47ee30fa20c0afc74f82d83aa508492d6128a4dbf7258e91960', transactionIndex: '0x37', blockHash: '0x4a83395b33fd6dd60302d8d340fd4afb114b4b1208cc94b5246ff39162e0173e', logIndex: '0x64', removed: false } 3. TransferBatchイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002', blockNumber: '0x7a2d5e', transactionHash: '0xd35a028d7c9a9d98d7ac103f6fa75b0496967e947791572b8a8a87b2407d78b2', transactionIndex: '0x8', blockHash: '0x1f9f0e0134a8b7c57156df86e84262c4037256a7f7f143a184f57922fd1ad3b1', logIndex: '0x13', removed: false } こちらはERC-1155のTransferBatchイベントで、複数のトークンが一度に移転されたことを示しています。詳細は Etherscan で確認できます。 イベントデータの活用方法 受信したイベントログを解析することで、以下のような情報を取得できます: ERC-721:  event Transfer  から直近の送信先を特定 ERC-1155: event TransferSingle  と  event TransferBatch  から所有者と所有量を計算 これらの情報をリアルタイムで処理し、データベースに記録することで、常に最新のNFT所有状況を把握することができます。 まとめ Ethereumフルノードを使ったWebSocket接続により、NFTの所有権移転をリアルタイムで監視できることがわかりました。この方法は、NFT取引プラットフォームやウォレットサービスなど、常に最新の所有権情報を必要とするアプリケーションで特に有用です。 参考資料 Geth PubSub API ドキュメント ERC-721 非代替性トークン規格 ERC-1155 マルチトークン規格   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [web3] Ethereum フルノードを使ったERC-721, ERC-1155の所有者のリアルタイム監視方法 first appeared on SIOS Tech. Lab .
こんにちは、エンジニアのわたなべです。 2025年1月~5月で LinuC の101, 102, 201, 202, 303 にスピード合格できたので、合格体験記としてまとめておきます。レベル1とレベル2の間に基本情報を取ったりしたので、LinuCに限ると4ヶ月で取得できたことになります。 ※ あくまで私のやってきたことを紹介する記事であり、これをやれば必ず合格できると保証する内容ではないことをご承知おきください。 学習開始時点(2025年1月)のバックグラウンドとしては以下の感じで、がっつりインフラ触ってるとかではなく経験浅めからのスタートなので、勉強時間についてはある程度参考になるかと思います。 新卒エンジニア2年目(非情報系学部) DB等のミドルウェア構築検証などでLinuxを触る経験は多少あり 基本コマンド(lsとかtouchとか)以外について、Linuxの知識を深める経験はなし ここからでも4ヶ月でレベル3認定までもらえましたので、学習方法を間違えずしっかり時間を確保できれば十分身に着けられます。 試験概要 LinuCとLPIC LinuC は Linux Professional Certification の略で、名前の通りLinuxの試験です。 LPIC とよく並べられますが、運営団体が違ったりします。試験範囲はおおよそ被ってたりと、どっち受けるかみたいな話はネットとかでよく話題になってます。 どうせ右足から歩き始めるか左足から歩き始めるかみたいな違いしかないと思うんですが、 自分の場合は新しくできた方が試験内容も新しいのかなというだけでLinuCにしました。実際201試験では LinuCのみdockerが範囲だったりとやや試験範囲に差分があり 、LinuCの方が新しめな所感です。 レベルと認定ついて 認定についてはちょっとややこしいので整理しておきます。 レベル1 LinuC レベル1は 101試験, 102試験の両方合格で認定 されます。 午前午後とかの同日で受ける必要は全くないので、101試験からチャレンジして合格したら102の勉強を始める感じです。 レベル2 レベル2は以下を両方満たすと認定されます。 有意なレベル1認定を持つこと 201試験、202試験両方合格すること LinuCの有意性は5年間なので、レベル1を取ってから5年以内に合格しないとまた101試験と102試験を受けないといけません。 レベル2の認定を受けると、自動的にレベル1の有意性も更新され、レベル2取得時から5年間へと延長されます。 レベル3 レベル3は以下を両方満たすと認定されます。 有意なレベル2 認定を持つこと 300試験、303試験、304試験の いずれか に合格すること レベル3 も認定をもらったタイミングでレベル2の有意性が更新されます。 レベル3だけ試験は1つで済みますね。 テーマがそれぞれ違っていて、 300試験 は混在環境、 303試験 はセキュリティ、 304試験 は仮想化&高可用性 となります。 それぞれ202試験との重複した範囲があり、意外とスムーズにクリアできます。リンクから試験範囲を確認して業務に関連深そうなものを見極めましょう。 この記事では303試験のみについて解説します。 学習コンテンツ 参考: LPI-Japan認定教材 レベル1,2 LPI-Japan 公式Youtube LPI-Japanさんのウェビナーのようなもののアーカイブですが、試験概要や各章について解説があります。 各章については試験に直結する知識や問題を提示してくれるというよりかは、 機能の役割など概要として理解するのに最適 です。 再生リストの順番がバラバラなので、サムネやタイトルから目的の動画を探して視聴してください。 あずき本 Linux教科書 LinuCレベル1 Version 10.0対応 LinuCレベル1の定番教科書。2つの試験範囲を完全網羅し、豊富な練習問題で合格をサポート。基礎から実務まで体系的に学習できる構成になっています。 ★★★★☆ 4.0 (116件のレビュー) 著者: 中島 能和, 濱野 賢一朗 Linux教科書 LinuCレベル2 Version 10.0対応 LinuCレベル2の認定テキスト。仮想マシンやネットワークの構築など、より高度な技術を解説。実践的なスキルを身につけられます。 ★★★★☆ 4.1 (48件のレビュー) 著者: 中島 能和, 濱野 賢一朗 Linux教科書、通称 あずき本 。 インプットと各章の問題、それと模擬試験が付いています。 まずは一読して、各章の問題を解きましょう。模擬試験は最後に。 スピードマスター LinuC レベル1 スピードマスター問題集 LinuCレベル1の対策問題集。472問の問題と丁寧な解説でテンポよく学習できる一冊。試験直前の総仕上げにも最適です。 ★★★★☆ 4.2 (82件のレビュー) 著者: 山本 道子, 大竹 龍史 LinuCレベル2 スピードマスター問題集 待望のLinuCレベル2対策問題集。475問を収録し、201・202試験の範囲を網羅。実力アップと弱点克服に効果的な構成です。 ★★★★☆ 4.1 (20件のレビュー) 著者: 大竹 龍史 スピードマスター問題集、通称 スピマス 、あるいは白本。 各章での問題と丁寧な解説、模擬試験が付いてます。 あずき本でインプットが出来たらこちらに着手します。 この時、解説をよく読んで不明点があればGoogle先生なり各種AIなりに聞いて疑問点を解消しましょう。 あずき本とスピマスの各問題が解けるようになったら、それぞれの模擬試験を解きます。 試験当日、全問題ほぼ100%解ければまず合格できるかと思います。 おまけ LPI-Japan 公式例題 LPI-Japan公式サイトに例題と解説があります。 上記の参考書のみでも合格できますが、スマホでスキマ時間に読み進めるといいでしょう。 レベル3 303試験 レベル3は学習コンテンツがぐっと減り、あずき本やスピマスがありません。 ping-tと公式例題だけでがんばりましょう。 ping-t 私は ping-tだけで合格できました。 スピマス同様、1週目は必ず解説をじっくり読みましょう。ping-t内でAIアシスタント解説機能がありますので、疑問点があればこちらも活用しましょう。 ping-tではこんな感じで問題の進捗度合いが換算されます。 レベル40/40 までブラッシュアップできれば、まず合格できるかと思います。 また、コマ問といって記述式の問題もあります。 コマ問の方はちゃんとやらなくて大丈夫です。 理由としては、①選択式問題をしっかりやって理解を深めていけば記述もある程度正答できる ②本番での記述問題は60問中3問程度と出題数が低い からです。 選択式に飽きたら息抜き程度に挟むといいです。 勉強時間 合格に要した勉強時間は以下の通りです。 あくまで私の場合ですので、自分のペースで計画を立ててみましょう。 101, 102試験 それぞれ 週20時間 × 3週間 = 計60時間 これは机に向かってあずき本・スピマスをこなした時間です。 この辺りはなんだかんだ基本コマンドに関する問いが多く、覚える量も少ないのでサクッととれました。 201, 202試験 それぞれ 週20時間 × 4週間 = 計80時間 特に202試験は範囲が膨大でありながら問われる内容もマニアックなので、鬼門です。心してかかりましょう。 ただ結局、覚えていれば解けるし覚えていなければ解けないものなので、ダラダラと時間をかけずに短期集中で取り組むことをオススメします。 303試験 週20時間 × 3週間 = 計60時間 303試験に限らずレベル3系は202試験と内容が重複しています。 ですので202試験まで突破できれば、意外とあっさり乗り越えられます。 ここまで来たらもう一息です。がんばりましょう。 さいごに 勉強に際してちょっとしたコツみたいなものがあります。 はじめは暗記量が膨大で絶望しますが、しばらく勉強を続けていくとコマンドの組み立て方やオプションなどで共通した考え方が見えてきます。 この感覚にきちんと向き合って言語化できれば、暗記量を節約できてきます。 例えば、ファイルの移動をする mvコマンド と、異なるホスト間でファイルをアップロード・ダウンロードする scpコマンド があります。 これは コマンド <移動元> <移動先> と覚えてしまえば、暗記量を節約できますよね。 こんな感じに 抽象化してパターン化できれば暗記しなきゃいけないことが減ってきます 。 ただ202あたりになってくると、 似たような操作パターンなのに全然違う ってことがかなり増えてきます。 そういったものはどうしても混同して間違えやすいです。ですので 発見するたびに書き出して、試験直前の確認シートにして しっかり復習して臨みましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【LinuC スピード合格体験記】LinuC 合格に必要な勉強時間・学習コンテンツ・学習方法まとめ first appeared on SIOS Tech. Lab .
こんにちは。サイオステクノロジー OSS サポート担当 山本 です。 今回は keycloak (というか SSO) ってどんなもの?というお題でざっくり話してみようと思います。 今日、インターネットを使用しているのであれば SSO の恩恵を受けていないということは稀かと思いますし、どんなものかもある程度以上知っている方も多いかと思います。 が、個人的に keycloak を調べてみる機会があって合わせて周辺の情報を確認しなおしたので、折角なのでざっくりとまとめてみようと思った次第です。 ぼんやりふんわりとした理解の助けになれば幸いです。 ■SSO ってなんだ? SSO (シングルサインオン) は、その名のとおり サインオン (=ログイン、サインインなど) に関する技術で、主に Web サービスや Web アプリケーションで活躍するものです。 改めてお話しするまでもないかとは思いますが、サインオンは そのユーザを識別する何らかのキー (例えば、「 ユーザ名 + パスワード 」など) によって ユーザを識別 し、そのユーザ向けのサービスを提供したり、そのユーザ自身が登録した情報にアクセスできるようにしたりするための処理です。 例えば、電子メールサービスを扱うならばそのユーザ宛てのメールやそのユーザ自身が作成したメールだけを見れるようにしなければいけませんし、電子書籍サービスを扱うならばユーザが購入した書籍を識別できなければいけません。 この他にも様々な観点において、Web サービスなどは ユーザを識別できなければ立ち行きません 。 このため、サインオンという考え方は Web サービスなどにおいて非常に重要なものとなります。 …と改めて前置きをしたところで、まずは SSO と普通のサインオンの違いを確認しておきましょう。 (以下では全てのサインオンは「ユーザ名 + パスワード」で行うものと仮定してお話しします。) ■通常のサインオンの場合 あるユーザが「HogeHoge メール」というサービスを利用しようとしたとします。 この場合、そのユーザは「HogeHoge メール」に登録した、 「HogeHoge メール」用のユーザ名 + パスワード でサインオンを行なってサービスを利用することになります。 その後、そのユーザが「FugaFuga 動画」というサービスを利用しようとしたとしましょう。 そうしたら、そのユーザは 別途 「FugaFuga 動画」に登録した、 「FugaFuga 動画」用のユーザ名 + パスワード でサインオンを行なってサービスを利用することになります。 ……それはそうだろ、という感じですね。 このように、従来型の通常のサインオンでは 使用するサービスごとに識別情報・認証情報の登録やサインオン操作が必要 になります。 ■SSO の場合 SSO では、” SSO 認証サーバ ” などと呼ばれるいわゆる 親玉 的なモノが出てきます。 この「親玉」は様々な 他のサービスと連携 し、 連携先のサービスのサインオン操作などを肩代わり します。 これにより、ユーザは 「親玉」用の 1種類の認証情報 (「ユーザ名 + パスワード」など) だけでその 「親玉」と連携しているあらゆるサービスを効率よく使うことができます 。 大雑把なイメージでのお話にはなりますが、少し細かく見てみましょう。 ■SSO の例1:サインオン操作の肩代わりと入力回数低減 あるユーザAが SSO 認証サーバ「PiyoPiyo 認証」と連携している「HogePiyo メール」というサービスを利用しようとしたとします。 そのユーザAが「HogePiyo メール」サービスを利用しようとすると、”「HogePiyo メール」から来た” という情報を付けた状態で「PiyoPiyo 認証」へ飛ばされます。 ユーザAは飛ばされた「PiyoPiyo 認証」で、「PiyoPiyo 認証」に登録した 「PiyoPiyo 認証」用のユーザ名 + パスワード でサインオンを行ないます。 「PiyoPiyo 認証」でのユーザ認証に成功すると、「PiyoPiyo 認証」は  ・「HogePiyo メール」に対して “(「PiyoPiyo 認証」の) ユーザ ID” などのようなユーザを識別する情報 (+ その他必要になる情報)  ・ユーザAに対して「HogePiyo メール」のページに飛ばす処理 と、このユーザAが「HogePiyo メール」を使用する際に、このユーザAからのアクセスであると「HogePiyo メール」が識別できる何らかの手段・情報をそれぞれに送ります。 これで、このユーザAは無事「HogePiyo メール」サービスを利用することができるようになります。 ここで、この ユーザAは「PiyoPiyo 認証」にしかサインオン処理を行なっていない のがポイントです。 さて、その後さらにこのユーザAが先と同じ SSO 認証サーバ「PiyoPiyo 認証」と連携している「FugaPiyo 動画」というサービスを利用しようとしたとします。 基本的な流れは先の「HogePiyo メール」の例と同じで、そのユーザAが「FugaPiyo 動画」サービスを利用しようとすると、”「FugaPiyo 動画」から来た” という情報を付けた状態で「PiyoPiyo 認証」へ飛ばされます。 さて、先ほどはここで「PiyoPiyo 認証」でサインオンを行ないましたが、このユーザAは先ほど「HogePiyo メール」を利用しようとした時に、 既に「PiyoPiyo 認証」へのサインオンを済ませています 。 このため、 改めて「PiyoPiyo 認証」用のユーザ名 + パスワードを 入力することなく 「FugaPiyo 動画」を利用できる状態になるというわけです。 概ねこのような流れで、SSO を利用すると ユーザは一度のサインオン操作で複数のサービスを利用できる ようになります。 なお、サービス側はあくまでサインオンを肩代わりしてもらっているだけであり、サービス自体の情報 (「HogePiyo メール」の管理しているメール情報や、「FugaPiyo 動画」の購入履歴・利用プラン情報など) は別途そのサービス自体が管理することになります。 このような役割を担う SSO の仕組みとしては、 Kerberos 認証 、 SAML 、 OpenID-Connect (OIDC) などが挙げられます。 ■SSO の例2:ユーザ情報の共有 先の例を流用して、例えば「FugaPiyo 動画」で “「PiyoPiyo 認証」のメールアドレスでメールマガジン登録” という操作ができる場合のことを考えてみましょう。 この場合、「FugaPiyo 動画」は「PiyoPiyo 認証」に登録されているメールアドレスを取得する必要があります。 (SSO 認証サーバ「PiyoPiyo 認証」には、ユーザ情報としてメールアドレスも登録されているものとします。) あるユーザAがこの操作を行うと、「FugaPiyo 動画」は「PiyoPiyo 認証」にこのユーザAのメールアドレスが必要な旨を伝えます。 もちろん、いくら連携しているとは言え「PiyoPiyo 認証」は(セキュリティ的に考えて)無条件でメールアドレスを渡すわけにはいきません。 そこで、「PiyoPiyo 認証」は “「FugaPiyo 動画」がユーザAのメールアドレスを取得しようとしているよ” という情報を含めた合わせ鍵を用意して、「FugaPiyo 動画」に渡します。 「FugaPiyo 動画」は「PiyoPiyo 認証」から受け取った合わせ鍵 (ユーザA用の部分) や、後で「FugaPiyo 動画」に戻る際のアドレスなどの情報をまとめて、ユーザAに送ります。 ユーザAに送られる情報には「PiyoPiyo 認証」に飛ばす処理も含まれているので、ユーザAは「PiyoPiyo 認証」に飛ばされます。(ユーザAが「PiyoPiyo 認証」にサインオンできていない場合、ここでサインオン操作をする必要があります。) ユーザAが合わせ鍵を「PiyoPiyo 認証」に渡すと、「PiyoPiyo 認証」は合わせ鍵の内容を確認して、 ユーザAに [「FugaPiyo 動画」にメールアドレスを渡すけど、いいのか?] と 確認を行ないます 。 ユーザAがこれを承認すると、「PiyoPiyo 認証」はこの合わせ鍵に「承認済み」フラグを記録し、ユーザAは「FugaPiyo 動画」に戻されます。 「FugaPiyo 動画」はユーザAが戻ってきたことを確認すると、改めて合わせ鍵を使って「PiyoPiyo 認証」にユーザAのメールアドレスを要求します。 「PiyoPiyo 認証」は合わせ鍵の正当性や「承認済み」になっているか、要求元が間違っていないかなどを確認した上で、問題がなければメールアドレスの情報を持っている リソースサーバ に “「FugaPiyo 動画」にユーザAのメールアドレスを渡すように” という通達と合わせ鍵を、「FugaPiyo 動画」にはリソースサーバの場所と合わせ鍵を渡します。 そうして、「FugaPiyo 動画」はリソースサーバに合わせ鍵を持ってアクセスすることで、目的のユーザAのメールアドレスを得ることができます。 ……行ったり来たりでややこしいですが、このほとんどは 使い捨てのデータによる承認や確認処理 で、  ・ユーザAは SSO 認証サーバ「PiyoPiyo 認証」で サインオンと承認をしただけ  ・やりとりされるのは ユーザが許可した メールアドレスのみ  ・実際にメールアドレスのやり取りをしているのは「FugaPiyo 動画」とリソースサーバのみ なので、ユーザは少ない手間で、かつデータのやり取りはかなり安全に行なうことができます。 と、基本的には概ねこのような流れで、SSO によって SSO 認証サーバが連携する範囲で登録したデータをやりとり させることができます。 ここではメールアドレスを例にしたためあまり便利そうには見えないかもしれませんが、例えば SNS の投稿を連携したり、荷物の宛先情報などを連携したり……などであればだいぶ便利そうに感じれる…ハズです。 このような役割を担う SSO の仕組みとしては、 OAuth が挙げられます。 ■SSO のメリットは? さて、ゴチャゴチャ話してみましたが、結局 SSO というのは何が嬉しいのかを考えてみましょう。 まず、ユーザは 認証情報の管理がラク になります。 SSO 認証サーバの連携する範囲内のサービス・アプリケーションで1回だけ認証処理をすればよく、当然覚えておく認証情報も1つだけで済みます。また、場合によってはユーザの入力した情報 (各種個人情報や SNS 等の投稿など) を別サービスから必要に応じて参照させることで、同じ情報を複数回入力する手間も削減することができることもあります。 旧来はサービス・アプリケーションごとに別々の認証情報を用意し、必要に応じて各サービスに別々で手動での情報入力を行う必要があったことを考えると、ユーザにとってはかなりラクになることでしょう。 SSO 認証サーバの管理者・連携先としては…… まず、組織内で複数サービスのサインオン処理の統合のために使っている場合、SSO 認証サーバのアカウントだけ管理すればよくなるので、個別のサービスごとにアカウント管理するよりも アカウント管理の手間が減る 可能性が高いです。 外部の SSO 認証サーバと連携する場合だと、自システムに認証情報を保存しなくて済みます。このため、(その他の個人情報などを取得していなければ) 漏洩して困る情報を手元に置かなくて済むようになり、万が一自システムでの情報漏洩が出てしまった場合でも対ユーザへの被害が深刻になりにくい可能性が考えられます。また、 ユーザ側の新規登録の手間が省かれる ので、 ユーザにサービス自体を触ってもらいやすくなる 可能性も考えられます。 このように、認証の一元化による管理の容易さとユーザビリティの向上が見込めることが SSO のメリットと言えるでしょう。 ただし逆に、万が一 SSO 認証の認証情報が漏洩した場合、連携先のサービスなども含めて丸ごと不正利用される恐れがあります。 そのため、SSO 認証サーバ自体、および SSO 認証サーバの通信や SSO の認証情報には十全なセキュリティ対策が求められます。 (もっとも、SSO により管理を一元化することでより複雑な認証情報・認証方法を採用しやすくなったり、逆に SSO を使用していない場合にユーザが認証情報をメモして残したり使い回しをしたり…といったことを考えると、SSO のセキュリティリスクは高いというわけではありません。) ■keycloak ってなんだ? keycloak は、正にここまでお話ししてきた “ SSO 認証サーバ ” の1種です。 SAML 、 OpenID-Connect (OIDC) 、 OAuth に対応しており、管理コンソールやユーザ連携、ユーザ制御などの機能を備えています。 今回は前置きが長かったので、管理コンソールとそれぞれ今回のお話の何をどれで設定するのかだけ確認します。 まず、デモ環境を立ち上げます。今回は手早く podman で作ってしまいましょう。 podman を導入して、以下のようなコマンドを実行すれば OK です。 $ podman run --name test -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.2.4 start-dev (このコマンドはセキュリティ設定などを省いて動かせる デモ用・開発用の環境を作成 するためのコマンドであり、実際に 運用する環境を作る場合は絶対に使用してはいけません 。) そうしたら、任意のブラウザで以下のアドレスにアクセスしてみましょう。 http://(IP アドレス):8080/admin サインオンの画面が表示されるので、サインオンしてみましょう。 初期ユーザのユーザ名/パスワードは、podman コマンドの “KC_BOOTSTRAP_ADMIN_USERNAME” と “KC_BOOTSTRAP_ADMIN_PASSWORD” で指定したものです。 (上記のとおりのコマンドだった場合、”admin” / “admin” です。) サインオンに成功すると表示されるのが keycloal の管理コンソールです。 今回は、マークをつけてある今回お話ししたものに対応している3つの要素が何に対応しているかだけ、最後に確認したいと思います。 ・ Clients この keycloak (=SSO 認証サーバ) と 連携するサービス・アプリケーション やその連携方法などを設定します。 連携先のサービス・アプリケーション側でも別途この keycloak と指定した方法で連携するように設定する 必要があります。 今回のお話では「HogePiyo メール」「FugaPiyo 動画」が該当します。 ・ Users この keycloak による SSO 認証のユーザ情報 (認証情報など) を設定します。 今回のお話では「ユーザA」の認証情報が該当します。 ・ Realm (※ “master” となっているプルダウン部分) 設定する SSO 連携の「枠」です。keycloak では複数の Realm を持つことができます。 先述の “Client” と “User” などは Realm ごとに管理されます。 確認できたら、先ほど立ち上げた検証用の keycloak コンテナは念のため削除しておきましょう。 $ podman stop test $ podman rm test ■最後に 今回は SSO と keycloak の概要についてお話ししてみました。 SSO について長々とお話ししましたが、今日日インターネットを使用しているのであれば、恐らくは恩恵にあずかっているはずです。 そう、「google でログイン」「Apple でログイン」「Facebook でログイン」などのような「~でログイン」というアレです。アレこそが、SSO という技術の典型と言えるでしょう。 keycloak はそんな SSO の “親玉” の部分を担うサービスです。 例に挙げたような外部にまで連携する超大規模 SSO サービス……なんてものはまず新しく立ち上げることはないでしょうから、 基本的にはお話の途中でちらりと出した「組織内のサインオン処理の統合」のために使用するものと考えてもらえばよいでしょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post わからないなりに理解したい keycloak①:そもそも SSO って何だ? first appeared on SIOS Tech. Lab .
はじめに お久です!皆さんAIにどれぐらい課金していますか?動画配信サービスより、AIサービスのほうが課金額が高くなっている龍ちゃんです。サービスごとに特色もあり、得意領域もそれぞれ異なるので、いっそのこと10万ぐらいぶっこんでしまいたいところですね。 さて!今回は、AIサービスを使うにあたって便利にしていこうというお話です。具体的な部分としては「SlackからNotebook LMに簡単にデータを取り込む方法」のプロトタイプについてです。 今回はベースのコンテンツを作成して、執筆はAIにやらせてみようかと思います。 なぜこのシステムを開発したのか? このプロトタイプを開発した背景には、AIサービスの活用における課題があります。特に、現在様々なAIサービスが登場し、それぞれの特色や得意分野が異なる中で、効率的な情報管理と連携の重要性が高まっています。 具体的な課題として、Google Docsを普段使用していないためのデータ連携の困難さや、Slackでの会話内容を効率的にまとめる必要性がありました。また、Slack AIは比較的高価である一方、既に契約済みのNotebook LMを有効活用したいという思いもありました。 そこで今回は、「SlackからNotebook LMに簡単にデータを取り込む方法」のプロトタイプ開発に着手しました。初期段階として、Slackのテキストメッセージのみを対象とし、スレッドを一つの単位として取り込む基本的な機能の実装を目指しています。 Notebook LMでは、一度紐づけたソースが更新された場合は手動でアクションを行う必要があるため完全な自動化には至っていません。ですが、Google Docsを起動することなく情報を転機できるため、AIと開発の連携をより上げることができると期待しています。 龍ちゃん あとは!この記事を読んで作りたくなったからですね! 使用技術 今回開発したシステムは、以下のような技術スタックを使用しています。まず、バックエンドフレームワークとしてNestJSを採用しました。これは既存のデプロイ環境との親和性を考慮した選択です。 @slack/web-apiライブラリ を利用することで、Slackとの連携を効率的に実装することができました。 Slack APIに関しては、主に3つの機能を活用しています。まず、Event Subscriptionsの reactions.item_added を使用してリアクションの検知を行います。次に、メッセージの取得には conversations.history でリアクション対象のメッセージを、 conversations.replies でスレッド内の返信を取得します。また、 chat.postMessage を使用してスレッドへの返信機能も実装しています。 Google Docs APIについては、サービスアカウントを利用して認証を行い、 documents.batchUpdate を使用してドキュメントへのテキスト追記を実現しています。これにより、Slackでの会話内容を自動的にGoogle Docsに記録することが可能になりました。 最終的に、Google DocsとNotebook LMを連携させることで、Slackの内容を活用してNotebook LMを効率的に使用できるようになります。 処理フロー 処理フローは以下の流れになります。 初期設定とメッセージ送信 ユーザーがSlackにメッセージを送信すると、リアクション追加の準備が整います。 Slack Events APIによる監視 バックエンドAPIでは、Slack Events APIを使用してSlackでの活動を監視します。具体的には: POST /api/slack/events エンドポイントでSubscription Eventを受信 reaction_added イベントをキャッチして処理を開始(特定のリアクションの場合は処理) メッセージ情報の取得 リアクションが追加されると、システムは以下の処理を実行します: Slack APIの GET Slack Message を呼び出し Slack APIを用いてメッセージを取得 conversations.history :リアクションがつけられたメッセージ取得 conversations.replies :リアクションをつけられたメッセージにスレッドがあればスレッドのメッセージも取得する Google Docs連携処理 取得したメッセージ情報をもとに、リアクション対応の処理を実行: Google Docs更新処理を開始 Service Accountを使用した認証で安全にアクセス ドキュメントの内容を更新 完了通知 処理完了後、Slackに結果を通知: chat.postMessage でリアクション対象への結果通知 処理結果確認のメッセージ送信 構築 ここでは、実際のシステム構築について詳しく説明していきます。プロトタイプとはいえ、実用的な機能を備えたシステムを構築することができました。以下、主要なコンポーネントごとに実装の詳細を解説していきます。部分的なコードを解説用に添付します。最終的なコードはGitHubのリポジトリとブログの最後に完成版のコードを張ります。 Google Docsの更新 Google Docsを操作するためにサービスアカウントを用いて認証を行っています。 サービスアカウントって何? サービスアカウントとは、ユーザーがログインしなくても、プログラムがGoogleのサービスにアクセスできるようにする、特別なアカウントのことです。人間でいうと「あなたは〇〇のタスクを代わりにやってくれる、もう一人の自分」のような存在です。これにより、システムが裏側で黙々と処理を進めたり、決められたタイミングで情報を記録したりといった、自動化がスムーズに実現できるようになるんです。 Google Docsへの更新処理は、主に以下のような流れで実装しています: まず、Google Cloud Platformでサービスアカウントを作成し、必要なAPIを有効化します。認証情報をJSONファイルとして取得し、これを使用してGoogle APIにアクセスします。NestJSからは、googleapisライブラリを使用してクライアントを作成し、適切なスコープ(documents、drive)を設定します。スコープとしては、以下を指定しています。 スコープ 説明 https://www.googleapis.com/auth/documents Googleドキュメント操作に必要なスコープ https://www.googleapis.com/auth/drive Googleドライブ操作に必要なスコープ サービスアカウントで割り振られたメールアドレスで操作したいGoogleドキュメントに、共有権限でドキュメントを共有します。 認証周りの処理はnest.jsの Configuration を通じて共通的に保持しています。 import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { docs_v1, google } from 'googleapis'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} private googleDocs: docs_v1.Docs; get googleDosc() { if (this.googleDocs) return this.googleDocs; const auth = new google.auth.GoogleAuth({ credentials: { client_email: process.env.DOCS_CLIENT_EMAIL, private_key: process.env.DOCS_PRIVATE_KEY.replace(/\\n/g, '\n'), // 環境変数から読み込む場合は改行コードを修正 }, scopes: ['https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/drive'], // 必要なスコープ }); this.googleDocs = google.docs({ version: 'v1', auth, }); return this.googleDocs; } get GoogleDocsID(): string { return this.configService.get('DOCS_ID'); } } 実際のドキュメント操作では、 documents.batchUpdate メソッドでDocsのトップに追記する形で更新します。この際、Slackから取得したメッセージの内容を適切なフォーマットに変換して追記します。 import { Injectable } from '@nestjs/common'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class DocsAccessService { constructor(private readonly env: EnvironmentsService) {} docs = this.env.googleDosc; async updateDoc(docId: string, text: string) { try { const response = await this.docs.documents.batchUpdate({ documentId: docId, requestBody: { requests: [ { insertText: { text: text, location: { index: 1, // Insert at the beginning of the document }, }, }, ], }, }); return response.data; } catch (error) { console.error('Error updating document:', error); throw error; } } } これにより、API経由で内容を自動的にGoogle Docsに記録し、後でNotebook LMで活用できる形式で保存することが可能になりました。 Slackにボットメッセージを送信する Slackからのメッセージ送信では、 Bot User OAuth Tokens を使用します。これには、 channels:history 、 chat:write 、 reactions:read の権限が必要です。 まずは、先ほど取得したトークンを環境変数として参照できるようにします。 import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} get SlackBotToken(): string { return this.configService.get('SLACK_BOT_TOKEN'); } } メッセージの送信方法は主に2種類あり、 chat.postMessage で通常のメッセージを、 chat.postEphemeral で一時的なメッセージを送信できます。Slack Botの捜査には @slack/web-api を使用しています。 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { private client: WebClient; constructor( private readonly env: EnvironmentsService, private readonly docService: DocsAccessService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async postMessage(channelId: string, text: string, threadTs?: string): Promise<void> { try { console.log(`Attempting to post message to channel ${channelId}${threadTs ? ` in thread ${threadTs}` : ''}`); await this.client.chat.postMessage({ channel: channelId, text: text, thread_ts: threadTs, // ここに返信したいメッセージのtsを指定 // optional: icon_emoji: ':robot_face:', // カスタムアイコンを使いたい場合 // optional: username: 'My Reaction Bot', // カスタムユーザー名を使いたい場合 }); // console.log('Message posted successfully:', result.ts); } catch (error) { console.error(`Failed to post message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } // あなただけに表示されています系メッセージ async postMessageEphemeral(channelId: string, text: string, threadTs: string, user: string): Promise<void> { try { console.log(`Attempting to post message to channel ${channelId}${threadTs ? ` in thread ${threadTs}` : ''}`); await this.client.chat.postEphemeral({ channel: channelId, user: user, text: text, thread_ts: threadTs, // ここに返信したいメッセージのtsを指定 // optional: icon_emoji: ':robot_face:', // カスタムアイコンを使いたい場合 // optional: username: 'My Reaction Bot', // カスタムユーザー名を使いたい場合 }); // console.log('Message posted successfully:', result.ts); } catch (error) { console.error(`Failed to post message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } } 二つのメソッドはほとんど同様の使い方ができます。明確な違いとしては、他のユーザーからの視認性・メッセージの修正の有無にあります。 特徴 chat.postMessage chat.postEphemeral 用途 チャンネルやスレッドに永続的なメッセージを投稿する 特定のユーザーに対してのみ一時的な(非公開の)メッセージを投稿する 可視性 そのメッセージが投稿されたチャンネルの全員に見える 指定した user のみに見え、他のチャンネルメンバーには見えない 持続性 チャンネルの履歴に残り、後から参照可能 ユーザーがSlackクライアントを再起動したり、セッションを終了したりすると消える可能性がある(Slackの保証はないが、一時的と認識すべき) 宛先指定 channel パラメータでチャンネルIDまたはユーザーID(DMの場合)を指定 channel パラメータでチャンネルID、user パラメータでメッセージを表示するユーザーID を指定 スレッド返信 thread_ts パラメータでスレッドに返信可能 thread_ts パラメータでスレッド内の特定のユーザーに返信可能 APIスコープ chat:write(通常) chat:write.public(パブリックチャンネルのみ) chat:write.private(プライベートチャンネルのみ) chat:write または chat:write.ephemeral メッセージの編集/削除 chat.update や chat.delete で後から編集・削除が可能 基本的に後から編集・削除する機能はない (表示されるかどうかはSlackクライアントに依存するため) リアクションイベントを受け付ける Events Subscriptionsを設定することで、Slackでのリアクションなどのイベントを検知できるようになります。今回のプロトタイプでは、メッセージへのリアクション追加・削除を監視するため、 reaction_added と reaction_removed イベントを使用しています。 処理としては、URL検証用リクエスト url_verification とイベントコールバック event_callback を取得する処理が割り振られています。 import { Body, Controller, Post, UseGuards } from '@nestjs/common'; import { SlackService } from './slack.service'; import { SlackBotSignatureGuard } from 'src/common/guard/slack-bot-signature/slack-bot-signature.guard'; import { SlackEvent } from '@slack/types'; @Controller('/api/slack/') export class SlackController { constructor(private readonly slackService: SlackService) {} @UseGuards(SlackBotSignatureGuard) @Post('events') async handleSlackEvents(@Body() payload): Promise<string | { challenge: string }> { // SlackのイベントがURL検証リクエストの場合は、challengeを返す // これにより、Slackがイベントサブスクリプションを確認できるようになります if (payload.type === 'url_verification') { console.log('URL Verification Request handled.'); return { challenge: payload.challenge }; } const event: SlackEvent = payload.event; // イベントのタイプがサポートされていない場合はログを出力して終了 // ここでは 'event_callback' タイプのみを処理する // 必要に応じて他のイベントタイプを追加することができます if (payload.type !== 'event_callback' || !event) { console.log('Unsupported Slack event type:', payload.type); return 'OK'; // Slackに200 OKを返却 } // イベントの処理を行う if (event.type === 'reaction_added') { // リアクションが 'notebooklm' の場合のみ処理を行う if (event.reaction == 'notebooklm') { this.slackService.updateDataSource(event); } return 'OK'; // Slackに200 OKを返却 } return 'OK'; // Slackに200 OKを返却 } } リアクションイベントの型定義は こちらのリファレンス を参照してください。リアクション追加判別後、特定のリアクションが追加されたときのみ処理を行うようにしています。リアクション追加イベントには、リアクション情報とリアクションがつけられた対象を特定するための情報(ts/channel)が含まれています。こちらを使用してメッセージを取得します。 Slack スレッドに送信されたメッセージをすべて取得 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { constructor( private readonly env: EnvironmentsService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async getMessage(channelId: string, ts: string): Promise<string> { try { const result = await this.client.conversations.history({ channel: channelId, latest: ts, limit: 1, inclusive: true, }); if (result.messages && result.messages.length > 0) { const message = result.messages[0]; if (message.thread_ts) { const repilesResponse = await this.client.conversations.replies({ channel: channelId, ts: message.thread_ts, }); const replies = repilesResponse.messages .map((reply) => { // TODO:BOTが返信したメッセージを除外する if (reply.bot_id) return ''; return reply.text ? reply.text : ''; }) .join('\n'); console.log('Replies retrieved successfully:', replies); return replies; } return message.text ? message.text : ''; } else { console.warn('No messages found for the given ts'); return null; } } catch (error) { console.error(`Failed to get message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } } Slackのメッセージ取得について、いくつかの重要な点と制限事項があります。リアクションイベント内に含まれるチャンネルIDとts(タイムスタンプ)で conversations.history を limit:1 で実行することで親スレッドを特定します。これはスレッド内にてリアクションイベントが発生しても、取得できるのは親スレッドの情報というSlack API特有の仕様です。 API仕様書はこちらになります 。 スレッドを含むメッセージを完全に取得するためには、スレッド全体を別途取得する必要があります。BOTからの自動返信もメッセージとして含まれる可能性があり、これが実際の利用者の会話の流れを把握する際に不都合を生じさせることがあります。そのため、メッセージのフィルタリングや処理方法について、慎重な設計が必要となります。今回は、 conversations.replies の結果でBOTの情報が含まれる場合はメッセージを除外しています。 API仕様書はこちらになります 。 リアクションイベントからGoogle Docsの取得までを一つの処理としてまとめる ここまで、個別の処理に切り分けて実装していました。最終的にコントローラーから処理を受け取るサービスとして一つの関数にまとめていきます。 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { private client: WebClient; constructor( private readonly env: EnvironmentsService, private readonly docService: DocsAccessService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async updateDataSource(event: ReactionAddedEvent): Promise<void> { const channelId = event.item.channel; const ts = event.item.ts; const message = await this.getMessage(channelId, ts); if (!message) { console.warn(`Message not found for channel ${channelId} and ts ${ts}`); } if (message != '') { const docId = this.env.GoogleDocsID; const text = `\n---\n${message}\n---\n`; await this.docService.updateDoc(docId, text); this.postMessage( channelId, `Notebook LMデータソースに追記しました: ${event.reaction} by <@${event.user}> \n\n${message}`, ts, // ここでスレッドのtsを指定 ); } else { this.postMessage( channelId, `現在テキストソースにしか対応していません。リアクションをつけたメッセージはテキストが空でした。`, ts, // ここでスレッドのtsを指定 ); } } } 今回解説した関数を利用して、イベントを受け取りGoogle Docsの更新・SlackへのBotによるメッセージ送信までの実装が完了しました。 エラーハンドリングと署名検証 エラーハンドリングと今後の課題について詳しく見ていきましょう。 Slackの署名検証 公式でも言及されていますが、アクセスがSlackからのものであることを確約する 必要があります。nest.jsではGuardsという機能を用いて、コントローラーにアクセスする前に事前に検証をすることができます。処理自体は公式の情報をもとに構築してあります。 必要になるのは、Slack Developerで取得することができる Signing Secret です。 import { CanActivate, ExecutionContext, Injectable, RawBodyRequest, UnauthorizedException } from '@nestjs/common'; import { createHmac, timingSafeEqual } from 'crypto'; import { Observable } from 'rxjs'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class SlackBotSignatureGuard implements CanActivate { constructor(private readonly env: EnvironmentsService) {} private readonly MAX_TIMESTAMP_AGE_SECONDS = 300; // 5分 (リプレイアタック防止のため) canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest<RawBodyRequest<Request>>(); const slackSignature = request.headers['x-slack-signature'] as string; const slackTimestamp = request.headers['x-slack-request-timestamp'] as string; const rawBody = (request as any).rawBody; // main.ts で bodyParser を設定して取得 if (!slackSignature || !slackTimestamp || !rawBody) { console.error('署名検証が失敗しました: 必要なヘッダーまたはボディが不足しています。'); throw new UnauthorizedException('署名検証が失敗しました: 必要なヘッダーまたはボディが不足しています。'); } // リプレイアタック const timestamp = parseInt(slackTimestamp, 10); const currentTime = Math.floor(Date.now() / 1000); // Unixタイムスタンプ (秒) if (Math.abs(currentTime - timestamp) > this.MAX_TIMESTAMP_AGE_SECONDS) { console.warn( // 日本語にしてエラーメッセージをわかりやすくする `Slackリクエストのタイムスタンプが古すぎるか、未来のものです。タイムスタンプ: ${timestamp}, 現在: ${currentTime}`, ); throw new UnauthorizedException('Slackリクエストのタイムスタンプが古すぎるか、未来のものです。'); } // Slackの署名検証 const baseString = `v0:${timestamp}:${rawBody.toString()}`; const hmac = createHmac('sha256', this.env.SlackBotSigningSecret); hmac.update(baseString); const computedSignature = `v0=${hmac.digest('hex')}`; if (!timingSafeEqual(Buffer.from(computedSignature), Buffer.from(slackSignature))) { console.warn('Slackの署名が無効です。'); throw new UnauthorizedException('Slackの署名が無効です。'); } return true; } } エラーハンドリング プロトタイプとしての現在の実装では、基本的なエラーハンドリングのみを実装していますが、本番環境での運用を想定した場合、以下のような設計が必要になりそうです。 Slack API関連のエラー処理: レート制限への対応とリトライロジックの実装 API接続タイムアウトの適切な処理 チャンネルアクセス権限エラーの処理 Google Docs API関連のエラー処理: 認証エラーの適切な処理とトークンリフレッシュ ドキュメント編集権限エラーのハンドリング API制限到達時の待機ロジック実装 システム全般のエラー処理: エラーログの構造化と保存 重要なエラーの管理者への通知システム システムの状態回復メカニズムの実装 これらのエラーハンドリングを実装することで、システムの安定性と信頼性が大幅に向上します。また、運用面でのトラブルシューティングも容易になります。 今後の展望 今回のプロトタイプ開発を通じて、SlackからNotebook LMへのデータ連携という基本的な仕組みは構築できました。しかし、これはあくまで第一歩であり、より実用的で価値のあるシステムに発展させるための道筋がいくつか見えてきました。 機能拡張の方向性 ファイル形式の対応拡大 現在はテキストメッセージのみの対応ですが、Slackでは画像、PDF、スプレッドシートなど様々なファイルが共有されます。特に、画像からのOCR処理やPDFの内容抽出機能を追加することで、より包括的な情報収集が可能になります。Google Cloud VisionやDocument AIとの連携により、これらの実装は十分現実的です。 リアクション種別による分類機能 現在は単一のリアクション( :notebooklm: )のみに対応していますが、複数のリアクションを使い分けることで、情報を自動分類できるようになります。例えば: :important: → 重要な情報として優先度高でマーク :todo: → タスクリストとして別ドキュメントに記録 :knowledge: → ナレッジベース用ドキュメントに整理 この仕組みにより、単なるデータ収集から、目的別の情報整理システムへと発展させることができます。 AI要約機能の統合 現在はSlackのメッセージをそのままGoogle Docsに転記していますが、長いスレッドや議論については、Azure OpenAIやAnthropic APIを活用した要約機能を追加したいと考えています。これにより、本質的な内容のみを抽出してNotebook LMに渡すことが可能になり、より効率的な情報活用が実現できます。 システム改善の取り組み パフォーマンスとスケーラビリティの向上 現在の同期処理から非同期処理への移行は必須です。Redis Queueやbull.jsを使用したジョブキューイングシステムを導入し、大量のメッセージ処理にも対応できる構成に変更予定です。また、Google Docsの容量制限を考慮し、定期的なドキュメント分割機能も検討しています。 ユーザーエクスペリエンスの改善 現在のシンプルなBot返信から、より詳細なフィードバック機能への拡張を計画しています。処理状況の可視化、エラー時の分かりやすい説明、さらにはSlashコマンドを使った手動操作機能なども追加したいところです。 監視とメンテナンス機能 本格運用を見据えて、システムヘルスチェック機能やログ分析ダッシュボードの構築も重要です。特に、API使用量の監視やエラー傾向の分析機能により、安定したサービス提供を目指します。 技術的挑戦 Notebook LM APIの活用 現在はGoogle Docsを経由した間接的な連携ですが、今後Notebook LM APIが公開された際には、より直接的な統合を検討したいと思います。これにより、リアルタイムでの質問応答機能や、自動的なインサイト生成なども実現可能になるかもしれません。 マルチプラットフォーム対応 Slack以外のコミュニケーションツール(Microsoft Teams、Discord、Mattermost等)への対応も視野に入れています。共通のインターフェースを設計することで、組織の使用ツールに関係なく同様の価値を提供できるシステムを目指します。 セキュリティとコンプライアンス強化 企業利用を考慮し、データの暗号化、アクセス権限の細分化、監査ログの充実などを進める必要があります。特に、個人情報や機密情報を含む可能性のあるSlackメッセージの取り扱いについては、慎重な設計が求められます。 おわりに 今回開発したプロトタイプは、AIを活用した情報管理の可能性を示す小さな一歩でした。しかし、ここから得られた知見と技術基盤を活かし、より実用的で価値のあるシステムへと発展させていきたいと考えています。 特に、現在のAIブームの中で、単にAIツールを使うだけでなく、既存のワークフローにAIを自然に統合する仕組みの重要性を改めて感じました。SlackのようなコミュニケーションツールとNotebook LMのような分析ツールを橋渡しすることで、日常的な業務の中で自然にナレッジが蓄積され、活用される環境を作ることができそうです。 今後も継続的に改善を重ね、最終的には「気がついたら素晴らしいナレッジベースができていた」と感じられるような、透明で価値のあるシステムを目指していきます。皆さんも、ぜひ様々なAIサービスを組み合わせて、独自の価値を生み出すシステム構築にチャレンジしてみてください! 弊社ではAI活用頑張ってますので、こちらも併せてチェック!! 2025-05-31 PRレビューを自動化しよう!GitHub Copilot × システムプロンプトの基本 2025-05-30 GitHub Copilotをチーム開発で使いこなす!システムプロンプト設定方法 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Notebook LMへのデータ収集をSlack Botで効率化する開発 with Google Docs first appeared on SIOS Tech. Lab .
先日、久しぶりにGoogle App Script(GAS)で作成したコードのメンテナンスのために、GASのエディタを開いた龍ちゃんです。既存のコードが動いているからと言って、自分がメンテしていない外部APIを使用していると、知らないうちに更新が入る可能性もありますよね。やはり失敗を検知する仕組みは必要ですね。 今回は「GASの実行可能API」についてのお話です。 GASの公開方法としては「ウェブアプリ」「実行可能API」の二つがあります。 実行可能APIとしてデプロイされたアプリを実行するためには、Google OAuth2.0を利用して認可を行い、アクセストークンを取得する必要があります。 実際にアクセストークンを取得する流れをステップバイステップで解説していきます。 前提条件 バックエンドはnest.jsを使用して作成しています。具体的なコードに関しては、ブログの最後に記載しておきます。 デプロイ先 想定している環境としては、クライアントをAzure Static Web App(SWA)、バックエンドをAzure App Seriveで構築しています。SWAとApp ServiceはAPIリンク(Bring Your Own Functions方式)で接続しています。APIリンクの場合は、バックエンドを /api にルーティングして同一ホストとして処理することができます。 ソースコードはGitに上がっています。 リポジトリ 説明 https://github.com/Ryunosuke-Tanaka-sti/2025-line-liff-frontend フロントエンド https://github.com/Ryunosuke-Tanaka-sti/2024-line-liff-app-backend/tree/main バックエンド 実行可能APIを作成する(Google App Script) 検証のために以下の簡易的な関数を作成してデプロイをします。 String : “Hello World!!”を返答する関数 Google Sheetに書き込まれている情報をすべて取得・返答する Google Sheetに一行追加する 実行可能APIとしてデプロイする前にGASのエディタ上で実行しておくと、デバッグすることができます。 Health Check Function:Hello Worldを返答する関数 非常に単純な関数です。 function healthCheckFunction() { return "Hello World!"; } getSheetAllData:Google Sheetに書き込まれている情報をすべて返答 GASからGoogle Sheetを操作して、Sheet内にあるすべての情報を取得する関数になります。GASからGoogle Sheetを操作する方法に関しては、こちらの「 Google Apps Script スプレッドシート編 初心者向け 」でまとめています。 function getSheetAllData() { // SHEET_ID・SHEET_NAMEの情報を更新してください const file = SpreadsheetApp.openById("SHEET_ID") const sheet = file.getSheetByName("SHEET_NAME") const lastRow = sheet.getLastRow() // 情報がなければスルー if (lastRow == 1) return[] const itemList = sheet.getRange(2, 1, lastRow - 1, 2).getValues() return itemList } insertDataToTargetSheet:Google Sheetに一行情報を追記する こちらはGoogle Sheetの一番最後の行に情報を追記します。こちらも同様に「 Google Apps Script スプレッドシート編 初心者向け 」でまとめています。 function insertDataToTargetSheet(url = "test") { // SHEET_ID・SHEET_NAMEの情報を更新してください const file = SpreadsheetApp.openById("SHEET_ID") const sheet = file.getSheetByName("SHEET_NAME") const lastRow = sheet.getLastRow() sheet.getRange(lastRow + 1, 1).setValue(url) } 引数でテキスト情報(URL)を受け取り、その情報をSheetに記入しています。 実行可能APIとしてデプロイする 実行可能APIとしてデプロイする前にプロジェクトをGCPと接続する必要があります。これはGASをAPI経由で実行する際、GCPプロジェクトから認可を発行して権限を確認するためです。 あとは、デプロイを作成しましょう。 GASが使用しているOAuthスコープを確認する プロジェクトの概要に移動するとGASプロジェクトが使用しているOAuthスコープを確認することができます。こちらは、実行可能APIとしてデプロイ後、外部からAPIをたたく際に取得するアクセストークンのスコープに収める必要があります。 今回であれば、以下のスコープになります。 スコープ 概要説明 https://www.googleapis.com/auth/script.external_request GASから外部のAPIへアクセスする際に必要(UrlFetchApp) https://www.googleapis.com/auth/spreadsheets GASからGoogle Sheetを操作するのに必要 Google Script Run実行 構築に必要なエンドポイントは4つになります。フロントエンド側から実行する順番に解説をしていきます。 有効なトークンを保持しているか検証エンドポイント /api/google-auth/verify 200の場合はトークン発行済み 401の場合は認可フロー開始:URL発行 Google認可フロー OAuth2.0認可用URL発行エンドポイント /api/google-auth OAuth2.0 Callbackエンドポイント /api/google-auth/callback Google Script Run実行用エンドポイント /api/google-auth/test ソースコードは長くなるので、最後にまとめて記載します。Gitのリポジトリとしては、 こちら を参照してください。 Google認可プロバイダー設定 Googleの認可プロバイダーの設定をする必要があります。「承認済みのJavaScript生成元」「承認済みのリダイレクトURI」は適宜設定してください。 ローカルで検証する場合は、以下の値を設定していました。赤枠の値は後で必要になるので、値をコピーしておいてください。 プロパティ 値 承認済みのJavaScript生成元 http://localhost:5000 承認済みのリダイレクトURI http://localhost:5000/api/google-auth/callback 次に API Library にアクセスして必要になるAPIを有効化させます。 Apps Script API を有効にしています。 nest.jsで開発するためには Google Auth Library を導入する必要があります。 npm install google-auth-library クライアントの作成には赤枠から情報を取得した情報を使用する必要があります。 import { OAuth2Client } from 'google-auth-library'; const client = new OAuth2Client({ clientId: "CLIENT ID", clientSecret: "CLIENT SECRET", redirectUri: "REDIRECT URI", }); 環境変数としてはConfigurationを使用して 保存しておけばアクセスがしやすくなります。 1. トークンを取得済みか検証する こちらのエンドポイントでは、Cookiesにトークンが保持されているかを確認します。Cookieに保存されているトークンを検証して、期限切れの場合は認可用のURLを発行して認可フローへ誘導します。 実装パターンとしては、401のエラーメッセージを拡張して認可用URLを埋め込んで返答しています。クライアント側で一度 /api/google-auth/veify を叩くことで認可まで一気に進めることができます。 2. Google認可フロー 認可フロー開始からアクセストークン取得までを一気に解説します。認可用URLにリダイレクトするとGoogleの画面が入るのでアカウント情報を入力すると、リダイレクトURIに設定したパスに認可コード付き(クエリ)でコールバックが返ってきます。 認可コードからIDトークンとアクセストークンを取得することができ、Cookiesに情報を保持します。Cookiesの保存期間としては1時間を保存期間としています。 最終的に好きな画面にリダイレクトさせれば完了です。 3. Google Script Run実行フロー 実行可能APIを外部から実行するためには、実行可能APIのスクリプトID・アクセストークン・実行したい関数名が必要になります。 公式リファレンスとしてはこちらになります 。 ヘッダーにアクセストークンを挿入して、URLはスクリプトIDを挿入したURLになります。 const response = await fetch(`https://script.googleapis.com/v1/scripts/${scriptId}:run`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ function: functionName, parameters: parameters || [], }), }); 送信するBodyの中身としては、 function で実行したい関数名を指定して、引数は parameters の配列に収めて送信することで渡すことができます。型定義としては以下になります。 { "function": string, "parameters": [ value ], } 検証用フロントエンド画面 クライアントで検証するために簡易的な画面を作成します。ソースコードの原文としては、 こちら に上がっています。 useGoogleOAuth:認可処理用カスタムHook "use client"; import { useEffect, useState } from "react"; export const useGoogleOAuth = () => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { const verify = async () => { const res = await fetch("/api/google-auth/verify"); if (res.status === 200) { setIsLoading(false); } else if (res.status === 401) { const data = await res.json(); console.log(data); window.location.href = data.url; } else { const data = await res.json(); alert(`認証に失敗しました。:${data.message}`); } }; if (isLoading && typeof window !== "undefined") { verify(); } }, [isLoading]); return { isLoading }; }; 処理は単純です。 /api/google-auth/verify にアクセスして、401が出たら認可用URIに遷移します。認可が完了するまでは isLoading で状態を管理します。 検証ページ "use client"; import { useActionState } from "react"; import { LoadingMainComponent } from "@/components/LoadingMainComponent"; import { useGoogleOAuth } from "@/hooks/useGoogleOAuth"; export default function GooglePage() { const { isLoading } = useGoogleOAuth(); if (isLoading) return <LoadingMainComponent />; const onClickRead = async ( action: "healthCheckFunction" | "getSheetAllData" ) => { const res = await fetch("/api/google-auth/test", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ functionName: action }), }); if (res.status === 200) { const data = await res.json(); console.log(data); } else { const data = await res.json(); alert(`Error: ${data.message}`); } }; return ( <> <main className="flex w-full flex-col gap-2"> <div className="flex max-w-xl flex-col gap-2 p-4"> <Button label={"Hello World!"} onClick={() => onClickRead("healthCheckFunction")} /> <Button label={"Get Sheet All Data"} onClick={() => onClickRead("getSheetAllData")} /> <FormComponent /> </div> </main> </> ); } type ButtonProps = { label: string; } & React.ButtonHTMLAttributes<HTMLButtonElement>; const Button = (props: ButtonProps) => { const { label, onClick } = props; return ( <button onClick={onClick} className="flex items-center justify-center rounded-lg bg-white px-8 py-2 shadow transition-all hover:-translate-x-1 hover:-translate-y-1 hover:cursor-pointer hover:shadow-md" > {label} </button> ); }; type FormType = { url: string; }; type PrevFormDataType = { value: FormType; validationError: { url: Error | null }; apiError: Error | null; }; const FormComponent = () => { const initialFormData: PrevFormDataType = { value: { url: "" }, validationError: { url: null }, apiError: null, }; const validationUrl = (url: string) => { try { new URL(url); return null; // URLが有効な場合はエラーなし } catch (e) { return new Error(`Invalid URL format ${e}`); // 無効なURLの場合はエラーを返す } }; const [formData, action, isPending] = useActionState< PrevFormDataType, FormData >(async (_: PrevFormDataType, formData: FormData) => { // FormDataをobjectに変換 const _formData = Object.fromEntries(formData.entries()); const data: FormType = { url: _formData.url as string, }; // validationを掛ける いい感じのライブラリがあれば参考にする const urlError = validationUrl(data.url); if (urlError) { return { value: { url: data.url }, validationError: { url: urlError, }, apiError: null, }; } // ここでAPI処理を実装・今回は2秒待ってエラーを返す const res = await fetch("/api/google-auth/test", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ functionName: "insertDataToTargetSheet", params: [data.url], }), }); if (res.status === 200) { alert("Data submitted successfully!"); return { value: { url: "" }, validationError: { url: null }, apiError: null, }; } const apiError = new Error("Failed to submit data"); return { value: { url: data.url }, validationError: { url: urlError, }, apiError: apiError, }; }, initialFormData); return ( <> <form action={action} className="flex w-full max-w-xl flex-col gap-2 rounded-md p-4 shadow" > <label className="flex flex-col"> <div className="flex flex-row text-xl"> <span className="w-1/3">名前:</span> <input className="w-full border p-1 text-right" type="text" name="url" defaultValue={formData.value.url} /> </div> <span className="h-4 text-xs text-red-500"> {formData.validationError.url && ( <>{formData.validationError.url.message}</> )} </span> </label> <button className={ "w-full rounded-md py-4 text-lg text-white" + (isPending ? " bg-gray-400" : " bg-blue-500") } type="submit" formAction={action} disabled={isPending} > 送信{isPending && "中"} </button> <span className="h-4 text-xs text-red-500"> {formData.apiError && <p>{formData.apiError.message}</p>} </span> </form> </> ); }; 実行可能APIの検証のために3つのパターンで /api/google-auth/test にリクエストを送信しています。 ソースコード 環境変数吸出し用env service import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { OAuth2Client } from 'google-auth-library'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} get GoogleClientID(): string { return this.configService.get('GOOGLE_CLIENT_ID'); } get GoogleClientSecret(): string { return this.configService.get('GOOGLE_CLIENT_SECRET'); } get GoogleRedirectUri(): string { return this.configService.get('GOOGLE_CALLBACK_URL'); } GoogleOAuth2Client() { const client = new OAuth2Client({ clientId: this.GoogleClientID, clientSecret: this.GoogleClientSecret, redirectUri: this.GoogleRedirectUri, }); return client; } get GoogleScriptURL(): string { return this.configService.get('GAS_SCRIPT_URL'); } get isProduction(): boolean { const env: string = this.configService.get('ENV'); if (env === 'development') { return false; } else { return true; } } } Guards import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { Request } from 'express'; @Injectable() export class IsGoogleIdTokenVerifyGuard implements CanActivate { constructor(private readonly env: EnvironmentsService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<Request>(); const idToken = request.cookies['id_token']; console.log('verify idToken', ':come on'); if (!idToken) return false; const isValid = await this.verfyIdToken(idToken); if (!isValid) return false; return true; } private async verfyIdToken(idToken: string): Promise<any> { const client = this.env.GoogleOAuth2Client(); try { const ticket = await client.verifyIdToken({ idToken: idToken, audience: this.env.GoogleClientID, }); const payload = ticket.getPayload(); const now = Math.floor(Date.now() / 1000); // 現在時刻(秒単位) if (payload && payload.exp && payload.exp > now) { return true; // トークンは有効 } else { return false; // トークンは無効または期限切れ } } catch (error) { return false; // トークンが無効の場合 } } } Controller import { Body, Controller, Get, Post, Query, Req, Res, UseGuards } from '@nestjs/common'; import { IsGoogleIdTokenVerifyGuard } from 'src/common/guard/is-google-id-token-verify/is-google-id-token-verify.guard'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { GoogleAuthService } from './google-auth.service'; import { RequestScriptRunDto } from './dto/request.dto'; @Controller('/api/google-auth/') export class GoogleAuthController { constructor( private readonly googleAuthService: GoogleAuthService, private readonly env: EnvironmentsService, ) {} // Google認証のURLを取得する @Get() async getGoogleAuthUrl(@Res() res): Promise<void> { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.redirect(authUrl); } // Google認証のコールバックURL @Get('callback') async getGoogleAuthCallback(@Query('code') code: string, @Res() res): Promise<void> { const tokens = await this.googleAuthService.getToken(code); res.cookie('id_token', tokens.id_token, { httpOnly: this.env.isProduction, secure: this.env.isProduction, sameSite: 'Strict', maxAge: 3600 * 1000, // 1時間 }); // 環境によってbooleanを切り替える res.cookie('access_token', tokens.access_token, { httpOnly: this.env.isProduction, secure: this.env.isProduction, sameSite: 'Strict', maxAge: 3600 * 1000, // 1時間 }); res.redirect('/community/google/'); } // Google認証のトークンを検証する @Get('verify') async verifyIdToken(@Req() req, @Res() res): Promise<void> { const idToken = req.cookies['id_token']; if (!idToken) { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.status(401).json({ message: 'No id_token', url: authUrl }); } // token validation const isValid = await this.googleAuthService.verfyIdToken(idToken); if (isValid) { res.status(200).json({ message: 'Valid access token' }); } else { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.status(401).json({ message: 'No id_token', url: authUrl }); } } @Post('test') @UseGuards(IsGoogleIdTokenVerifyGuard) async test( @Req() req, @Body() body: RequestScriptRunDto, @Res() res, ): Promise<string | undefined | { url: string; content: string }[]> { const accessToken = req.cookies['access_token']; console.log('accessToken', req); const result = await this.googleAuthService.runScript(accessToken, body.functionName, body.params); return res.status(200).json(result); } } Service import { Injectable } from '@nestjs/common'; import { Credentials } from 'google-auth-library'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class GoogleAuthService { constructor(private readonly env: EnvironmentsService) {} async getGoogleAuthUrl(): Promise<string> { const client = this.env.GoogleOAuth2Client(); const authUrl = client.generateAuthUrl({ scope: [ '<https://www.googleapis.com/auth/userinfo.profile>', '<https://www.googleapis.com/auth/script.scriptapp>', '<https://www.googleapis.com/auth/script.external_request>', '<https://www.googleapis.com/auth/spreadsheets>', ], redirect_uri: this.env.GoogleRedirectUri, }); return authUrl; } async verfyIdToken(idToken: string): Promise<any> { const client = this.env.GoogleOAuth2Client(); try { const ticket = await client.verifyIdToken({ idToken: idToken, audience: this.env.GoogleClientID, }); const payload = ticket.getPayload(); const now = Math.floor(Date.now() / 1000); // 現在時刻(秒単位) if (payload && payload.exp && payload.exp > now) { return true; // トークンは有効 } else { return false; // トークンは無効または期限切れ } } catch (error) { console.error('Error verifying access token:', error); return false; // トークンが無効の場合 } } async getToken(code: string): Promise<Credentials> { const client = this.env.GoogleOAuth2Client(); const tmp = await client.getToken(code); console.log(tmp); const { tokens } = tmp; return tokens; } // <https://developers.google.com/apps-script/api/reference/rest/v1/scripts/run?hl=ja> async runScript( accessToken: string, functionName: 'healthCheckFunction' | 'getSheetAllData' | 'insertDataToTargetSheet', parameters: (string | number)[] | undefined, ): Promise<string | undefined | { url: string; content: string }[]> { const url = this.env.GoogleScriptURL; const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ function: functionName, parameters: parameters || [], }), }); const data = await response.json(); if (response.status !== 200 || data.error) { console.error('Error calling Google Apps Script:', data.error); throw new Error(`Error: ${data.error.message}`); } const result = data.response.result; if (typeof result === 'undefined') return; if (typeof result === 'string') return result; if (Array.isArray(result)) { const temp = result.map((item: { url: string; content: string }) => { return { url: item.url || '', content: item.content || '', }; }); return temp; } return result; } } おわり GASを実行可能APIとして公開し、OAuth2.0による認証を実装することで、セキュアなAPIエンドポイントを作成することができました。今回実装したコードは、Google Sheetsとの連携も含めて、実際のプロダクションで使用可能なレベルのものとなっています。 今後は、エラーハンドリングやログ機能の追加など、より堅牢な実装に向けて改善を進めていきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GAS × OAuth2.0:実践で使える実行可能API構築の手順 first appeared on SIOS Tech. Lab .
OSSよろずサポート担当の神﨑です。 お問い合わせとしてよく来るエラーメッセージについて解説していこうと思います。 今回は、AH02608、AH10154、AH01095 AH02608: read request body failed このエラーは何のエラー? クライアントからのリクエストボディの読み取りに失敗した旨のメッセージです。リクエストボディの送信が途中で中断してしまった場合や、ネットワークに問題があった場合などに出力されます。 AH2609 と何が違うの? 同じエラーメッセージが出力されるエラーとして AH2609 があります。 AH2609 は Content-Length ヘッダー (事前にリクエストボディのサイズが指定される)で送信される際に、事前に指定されたサイズと違う場合や通信の切断が発生した場合に出力されます。 AH2608 はリクエストボディの形式がチャンクエンコーディング (事前にリクエストボディのサイズの指定がない)の場合のエラーです。 AH10154: pass request body failed このエラーは何のエラー? バックエンドへのリクエストボディ送信が失敗した旨のメッセージです。AH02608 の事象によりリクエストボディが正しく読み取れず、バックエンドへ送信できなかった可能性があります。 AH01095: prefetch request body failed このエラーは何のエラー? クライアントからのリクエストボディの事前取得に失敗してしまった旨のメッセージです。AH02608 の事象によりリクエストボディが正しく読み取れず、事前取得できなかった可能性があります。 3つのエラーはどう違うのか? どれもリクエストボディの処理が失敗しているメッセージとなりますが、読み取り、事前取得、送信と出力される処理のフェーズに違いがあります。 どう対策すればいいの? ネットワークの設定や、Timeout ディレクティブを見直したり、リクエストをクライアント側が強制終了しなかったかなどを確認する。 Timeout ディレクティブは以下の値を目安に変更する。 クライアント側のリクエスト全体を受信する時間>Timeout ディレクティブ 参考 AH02608、AH10154 については以下もご確認ください httpd mod_proxy logs ‘Partial results are valid but processing is incomplete’ error – Red Hat Customer Portal ※Red Hat社の有料ポータルログインIDが必要です。 AH02609 については以下もご確認ください。 Getting error “AH02609: read request body failed” in Apache HTTPD mod_proxy – Red Hat Customer Portal ※Red Hat社の有料ポータルログインIDが必要です。 AH1095 については以下もご確認ください。 Intermitent timeout errors in Apache HTTPD during request body prefetch ※Red Hat社の有料ポータルログインIDが必要です。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [Apache エラー解説]プロキシを使用する際によく出力されるエラーの原因と対策 first appeared on SIOS Tech. Lab .
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 以前公開した以下の記事では、ローカル環境で動作するオリジナルの MCP サーバーを開発する手順を案内しました。今回はその続編として、ローカル MCP サーバーを Remote MCP サーバーへ改良し、リモートサーバーで動作させる方法を共有します。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab 構成概要 筆者が動かした際の主な構成要素は以下の通りです。 Windows 10 Professional WSL 2.4.12.0 Ubuntu 24.04.2 LTS Node.js v22.15.0 Claude desktop for Windows version 0.9.3 Visual Studio Code version 1.99.3 ハンズオンを構成する環境は以下の通りです。 MCP ホストは、Claude desktop for Windows のネイティブアプリ版を利用します。 2025年5月現在、Web 版 Calude で Remote MCP サーバーを利用できるのは、Max、Team、Enterprise プランのみです。Pro、および Free プランにはまだロールアウトされてないため、desktop 版を利用します。 リモートMCPを使用したカスタム統合について | Anthropicヘルプセンター Claude から Remote MCP サーバーへは、remote-mcp の npm パッケージを介して連携するため、Windows 環境に Node.js を必要とします。 Remote MCP サーバーの構成における主な考慮事項は以下の通りです。 WSL 環境に構築する Ubuntu を、Remote MCP サーバーが動作するリモートサーバーとして扱います。 Node.js の Express パッケージを利用して HTTP プロトコルで 8787 ポートをリッスンします。 トランスポートには、ローカル MCP サーバーの STDIO から、Remote MCP サーバーでは Server-Sent Events(SSE)を使います。 Remote MCP サーバーで提供するツールの機能は、改良前の MCP サーバーと同じです。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab ハンズオンの手順で作成する Remote MCP サーバーの設定ファイルやソースコードは、以下の GitHub リポジトリで公開しています。手順と合わせてご確認ください。 hands-on-mcp-sios-apisl @ GitHub v2.0.0 :改良後の Remote MCP サーバー(SSE)の完成状態 v1.0.0 :完了前のローカル MCP サーバー(STDIO)の完成状態 基礎環境の構築 Remoe MCP サーバー環境 on WSL WSL 環境の Ubuntu 側に Remote MCP サーバーの稼働環境を構築します。 WSL 環境の構築 以下手順を参考に Windows PC へ Linux ディストリビューション(Ubuntu)環境を用意します。 初期環境構築: WSL 環境 on Windows 10 Node.js インストール JavaScript で実装された Remote MCP サーバーを実行するために Ubuntu へ Node.js をインストールします。 $ sudo apt install -y npm $ node -v v18.19.1 apt でインストールされる Node.js はバージョンが古いためアップデートします。 $ sudo npm install n -g $ sudo n 22.15.0 インストールしたコンソールを一度閉じて新たにコンソールを開き直し、導入したバージョンをセッションに反映します。 $ node -v v22.15.0 MCP ホスト環境 on Windows Windows 側に MCP ホストの稼働環境を構築します。 Claude desktop for Windows インストール MCP ホストに Web 版ではなくアプリ版の Claude desktop for Windows を利用するため、以下公式手順を参考に Windows PC へインストールします。 デスクトップ版Claudeのインストール | Anthropicヘルプセンター Node.js インストール MCP ホストの Claude から Remote MCP サーバーのツールへアクセスする際に、Node.js の「remote-mcp」npm パッケージを介して実行するため、以下手順を参考に Windows PC へ Node.js をインストールします。 初期環境構築: Node.js on Windows Visual Studio Code インストール WSL 環境の Ubuntu で TypeScript ソースコードを実装するためのエディターとして、Windows 環境にインストールした Visual Studio Code (VS Code) を WSL 環境へリモート接続して利用する方法をお薦めします。VS Code のインストール手順については、ネット上でたくさんのコンテンツが親切に説明されているので、本資料の範囲外とさせて頂きます。 Visual Studio Code インストール – Google 検索 なお、VS Code 以外のお気に入りのエディターを利用される場合は、文中の VS Code をご利用のエディターに置き換えて読み進めてください。 Remote MPC サーバーへ改良 WSL 環境の Ubuntu で Remote MCP サーバーを開発するため、Ubuntu にログインし、ハンズオン用のフォルダを作成して作業を進めます。 $ mkdir -p ~/handson/ $ cd ~/handson/ 改良前のローカル MCP サーバーを用意 以前掲載した以下の記事を参考に、Remote MCP サーバーの改良元となるローカル MCP サーバーを Ubuntu 環境に用意してください。なお、当該記事は Windows 環境で動作するローカル MCP サーバーの構築について記載していますが、今回の Remote MCP サーバーは Ubuntu 環境で動作させるため、Ubuntu 環境に用意してください。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab また、改良前のローカル MCP サーバーのソースコードは、以下の GitHub リポジトリに「v1.0.0」タグで保存しているので、Git コマンドで取得して用意頂くこともできます。 hands-on-mcp-sios-apisl v1.0.0 @ GitHub $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git -b v1.0.0 プロジェクト設定の改良 改良作業前に、現在設定されている「 package.json 」に従い依存パッケージをインストールします。 $ npm install Remote MCP サーバーの改良に当たり、追加で必要になる依存パッケージをインストールします。 $ npm install express @types/express ソースコードの改良 Remote MCP サーバーで起動するように「 src/index.ts 」の3か所を改良します。 まずは1点目の改良として、主に利用するトランスポートに関連する依存パッケージを置き換えます。以下の差分イメージでは、行頭の「+」は追加する行、「-」は削除する行を表します。 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; + import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; + import express from "express"; import { z } from "zod"; : 次に2点目の改良として、STDIO トランスポートの実装を削除します。 - async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCP Server running on stdio"); - } - - main().catch((error) => { - console.error("Fatal error in main():", error); - process.exit(1); - }); 最後の3点目の改良として、STDIO トランスポートが実装されていた箇所に、HTTP で通信する Server-Sent Events (SSE) トランスポートを実装します。 + const app = express(); + const port = 8787; + let transport: SSEServerTransport | null = null; + + app.get("/sse", async (_, res) => { + console.log("Received connection"); + transport = new SSEServerTransport("/message", res); + await server.connect(transport); + }); + + app.post("/message", async (req, res) => { + console.log("Received message"); + transport?.handlePostMessage(req, res); + }); + + app.listen(port, () => { + console.log("Remote MCP server listening on port", port); + }); 改良後のできあがりは以下を参考にしてください。 src/index.ts ビルドしてトランスパイル 改良後のできあがりを動作確認するために、ビルドして TypeScript から JavaScript のソースコードを生成します。 $ npm run build > hands-on-mcp-sios-apisl@1.0.0 build > tsc MCP Inspector で動作確認 MCP Inspector と Remote MCP サーバーを起動するには、2つのコンソールを使用します。まず、1つ目のコンソールで MCP Inspector を起動します。 $ npx -y @modelcontextprotocol/inspector@latest Starting MCP inspector... Proxy server listening on port 6277 MCP Inspector is up and running at http://127.0.0.1:6274 コンソールログに表示された URL のホスト部「127.0.0.1」を「localhost」に置き換え、その URL(例: http://localhost:6274 )でブラウザから MCP Inspector にアクセスします。 次に2つ目のコンソールを立ち上げて Remote MCP サーバーを起動します。 $ node build/index.js Remote MCP server listening on port 8787 Received connection MCP Inspector をアクティブにして、Transport Type 項目に「SSE」を選択し、URL 項目は Remote MCP サーバーを起動したホスト名とコンソールログに表示されたポート番号に「/sse」のパスを使った URL(例: http://localhost:8787/sse )を入力します。「Connect」ボタンをクリックすると MCP Inspector が Remote MCP サーバーに接続します。 接続したら画面中央の「List Tools」ボタンをクリックするとツール一覧が表示されます。 それ以降の動作確認は、ローカル MCP サーバーと同じため、以前の記事を参考に確認を進めます オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab > MCP Inspector で動作確認 Claude(MCP ホスト)に組み込み 連携する MCP サーバーの設定 オリジナルの Remote MCP サーバーを Claude から利用できるように設定ファイルを編集するため、Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面左上のハンバーガーメニューから「ファイル > 設定…」を選択します。 オリジナルの Remote MCP サーバーが利用できるようにするために、「開発者」タブの「構成を編集」ボタンをクリックします。 エクスプローラーが立ち上がり、Claude の設定ファイルに該当する「 claude_desktop_config.json 」ファイルをダブルクリックしてエディターで開きます。 「 claude_desktop_config.json 」ファイルにオリジナルの Remote MCP サーバーを利用するための設定を書いて保存します。 { "mcpServers": { "mcp-remote-sios-apisl-demo": { "command": "npx", "args":[ "mcp-remote", "http://localhost:8787/sse" ] } } } 「 claude_desktop_config.json 」ファイルの編集における主な考慮事項は以下の通りです。 「mcpServers > mcp-sios-apisl-demo > args」フィールドで主な考慮事項は以下の通りです。 npx コマンドの第1引数に mcp-remote パッケージを指定することで、Retemo MCP サーバーと連携します。 npx コマンドの第2引数に mcp-remote パッケージが連携する Remote MCP サーバーの URL を指定します。 Claude の再起動で設定反映 変更した設定ファイルの適用に、ウィンドウの「×」ボタンで終了させるだけではなく、Claude のプロセスを完全に終了させる必要があるため、Windows タスクバーの画面右側のアイコン表示領域から Claude アイコン(トゲトゲのウニの様なデザイン)を探し、右クリックで表示されるメニューから「終了」を選択してプロセスを完全に終了させます。 Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面中央のテキストボックス内の左下にある「検索とツール」アイコンをクリックします。表示されたメニューに、「mcp-remote-sios-apisl-demo」の項目と右横に数字が表示されていたら、オリジナルの MCP サーバーは正常に設定できました。 オリジナルの Remote MCP サーバーのデモ 提供するツールの機能は Remote MCP サーバーでもローカル MCP サーバーと同じため、以前の記事を参照ください。 (動画内で MCP サーバーを有効化する際、表示されているサーバー名『mcp-sios-apisl-demo』を『mcp-remote-sios-apisl-demo』に読み替えてご覧ください) オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab > オリジナルの MCP サーバーのデモ まとめ リモート MCP サーバーの実装手順は、いかがでしたでしょうか?MCP はまだ発展中の仕様のため引き続き動向をウォッチしながら得られたノウハウを共有していきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post オリジナルのちょっと便利な『リモート MCP サーバー』を作ってみた first appeared on SIOS Tech. Lab .
ども!AI関連のブログを頑張って執筆中の龍ちゃんです。最近は、設計から開発まで幅広い範囲でのAI活用をしています。一向に仕事がなくなる気配がないですね。AIが仕事を奪うのはいつになるのでしょうか。それまでは、活用して業務効率化していかないといけないですね。 さて!今回は「GitHub CopilotのPull Request(PR)レビューにシステムプロンプトを与える」という内容になっています。GitHub Copilotをレビュワーとして活用している方の中に以下のような課題感を抱えた方がいれば、興味のある内容だと思います。 英文でのレビューを日本語化したい CopilotのPRの観点があいまい コードレビューの品質を一定化したい GitHub Copilotのレビューを向上させる GitHub Copilot on VSCodeでシステムプロンプトを追加する 「 GitHub Copilotにシステムプロンプトを挿入する方法 」についてはこちらで触れています。こちらでは、 .github/copilot-instructions.md というファイルを作成して自然言語でシステムプロンプトを設定しています。残念ながら、上記の方法ではPR発行時に読み込んでくれません。 GitHub Copilotにシステムプロンプトを追加する GitHub Copilot PRレビューにシステムプロンプトを組み込む方法としては、PRに直接記述する必要があります。 # 実施事項 <!-- ここに人間向きのPRを書く --> <!-- for GitHub Copilot review rule --> 日本語で記載してください。(Copilot向けの指示) <!-- for GitHub Copilot review rule--> PRの観点が一定の場合は、テンプレートとしてリポジトリ単位で保存しておくと効果的です。PRのテンプレートを作成する方法としては複数あります。 リポジトリのルート直下に pull_request_template.md を配置 docs ファイル直下に pull_request_template.md を配置 .github ファイル直下に pull_request_template.md を配置 docs と .github ファイルの場合は、 PULL_REQUEST_TEMPLATE というファイルを生成することで複数のテンプレートを作成することができます。 copilot-instructions と pull_request_template の違い これらの2つのファイルは、それぞれ異なる役割と特徴を持っていますが、上手く組み合わせることで効果的なPRレビュー環境を構築することができます。以下の表で違いについてまとめます。 項目 .github/copilot-instructions.md pull_request_template.md 用途 Copilot(AI)に対するシステムプロンプト・カスタム指示を設定し、PR作成やCopilot Chatなどで自動的に反映させる PR作成時の説明・チェックリスト・レビュールールなどを人間・AIの両方に提示するテンプレート 主な対象 Copilot(AI) PR作成者・レビュアー・Copilot(AI) 記述場所 .github/copilot-instructions.md .github/pull_request_template.md (または他の指定ディレクトリ) 反映タイミング Copilot ChatやAIレビュー、要約生成などCopilotの応答時 PR作成時にPR本文へ自動挿入される 人間への見やすさ 人間は通常直接見ない PR本文に表示されるため人間も確認可能 運用の柔軟性 リポジトリ全体に一括でAI指示を適用できる 複数テンプレートや内容のカスタマイズが容易 主なメリット AI応答の一貫性・自動化 PR作成の標準化・レビュープロセスの明確化・人間とAI両方に伝達可能 主なデメリット 人間には直接見えない・AIが必ずしも全て反映するとは限らない 指示がPR本文に残るためノイズになる場合も・AIへの伝達は工夫が必要 GitHub Copilot PR用プロンプト備忘録 こちらはまだ検証中の内容になります。運用を進めてみて、進展があればブログにてまとめて行きます。 コメントは絶対日本語でほしい これは、マストで入れておきたい内容です。別に翻訳アプリを使うので、読めなくはないのです。ただ圧倒的に気分が駄々下がりになるので、絶対日本語化はしておきたいです。 絶対日本語で出力してください。 無視しておきたいことを「禁止事項」として追記 これは、開発時における不満です。Typescript環境で console.log を一生注意されるんです。検証時には残しておきたいですが、気分的にコンソールでトークン数が消費されるの気持ち的にげんなりです。 そのため、不要な指摘を減らすために以下のようなプロンプトを追加しています。 以下の点については、レビューの対象外としてください: - console.logの使用 - 開発環境用の一時的なコメントアウト - デバッグ用の一時的な変数 このように明示的に除外項目を指定することで、より効率的なレビューが可能になります。 もちろん、最終的なコードには含まれていては困るので改めてレビュー観点への追加対応を行う必要はあります。 おわり 今回は、GitHub CopilotのPRレビューをより効果的に活用するための方法として、システムプロンプトの追加方法とPRテンプレートの活用について紹介しました。AIと人間の両方に役立つ指示を設定することで、レビューの品質向上と一貫性を実現できます。これらのツールを上手く組み合わせることで、より効率的な開発プロセスを構築していきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PRレビューを自動化しよう!GitHub Copilot × システムプロンプトの基本 first appeared on SIOS Tech. Lab .
お疲れ様です。最近はブログから離れて隠遁生活をしていました。社内で「GitHub Copilot」の普及のためにイベントなどをやっていました。基本的な部分から実践的な使い方までをまとめていました。今回は「GitHub Copilot」でシステムプロンプトを設定して、チーム内で一定のGitHub Copilot出力を担保する方法について紹介していきます。 今回紹介する方法はパブリックプレビュー状態なので、変更される可能性があります。 最新情報 を追って合わせて更新していきましょう。 前提条件 開発環境としてはVisual Studio Code(VSCode)でGitHub Copilot businessでの開発を前提としています。 システムプロンプト設定方法 システムプロンプトを設定する方法としては、2ステップで設定することができます。 VSCodeの設定で「 Code Generation: Use Instruction Files 」の設定を有効化する .github/copilot-instructions.md を作成してシステムプロンプトを設定する VSCodeの設定有効化 VSCodeで「Shift+,」を開いて設定を開きましょう。検索欄にgithub.copilot.chat.codeGeneration.useInstructionFilesと入力するか、 こちら をクリックして設定項目に遷移してください。 項目を true にすると設定自体は完了です。 .github/copilot-instructions.md を作成してシステムプロンプトを設定する ルートディレクトリに .github ディレクトリを作成し、 copilot-instructions.md というファイルを作成します。作成したファイルに自然言語でプロンプトを記述することで機能が有効化されます。以下のようなディレクトリ構成になっていれば設定自体は完了です。 わかりやすいようにエンタメよりのシステムプロンプトを設定してみました。index.htmlの解説をしてもらうと以下のような出力が返答されており、適切に設定が完了しています。 GitHub Copilotの出力サンプル 入力 class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-6 上記のソースを解説して 出力 以下のコードは、HTMLの <header> 要素に適用されているクラス属性の一部です。このクラス属性は、Tailwind CSSを使用してスタイルを設定しています。 class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-6 bg-gradient-to-r : 背景にグラデーションを適用します。 to-r は「右方向へのグラデーション」を意味します。 from-indigo-600 : グラデーションの開始色を指定します。 indigo-600 はTailwind CSSで定義されたインディゴ色の濃いトーンです。 to-purple-600 : グラデーションの終了色を指定します。 purple-600 はTailwind CSSで定義された紫色の濃いトーンです。 text-white : テキストの色を白に設定します。 py-6 : 上下のパディングを設定します。 py は「padding-y(上下方向のパディング)」を意味し、 6 はTailwind CSSのスケールに基づく値(約1.5rem)です。 このクラス設定により、ヘッダー部分に右方向へのインディゴから紫へのグラデーション背景が適用され、テキストは白色で、上下に適度な余白が確保されていますにゃー。 こちらのファイルはそのままリポジトリに上げることで、新たに参入する開発メンバーも自然に導入することができます。 システムプロンプトの備忘録 こちらはまだ検証中の内容になります。運用を進めてみて、進展等があればブログにてまとめて行きます。 不必要なプロンプトは除外しておく 今回はデモ目的だったので、語尾に「にゃー」とつけてもらいました。実用性に関しては皆無であり、トークン数の無駄な消費につながります。GitHub Copilotはコード関連に特化したAIとなっています。そのため、汎用的な質問などは受け付けません。 システムプロンプトには、コードに関する情報を入れておくことが好ましいですね。 フロントエンドエンジニアならこんなプロンプトもよいかも 前提情報として、リポジトリ内で使用している技術の概要あたりは書いておくのが良いかもしれません。GitHub Copilotは特に意識をしなければ、開いているファイル群を読み取り回答を生成します。Tailwindでカスタム定義したCSSなどは、あまり読み込んでくれない印象です。そのため、以下のような情報を箇条書きで与えています。 Next.js+Typescript+Tailwind CSS構成 Tailwind CSSベースでのスタイル情報 コンセプト(モダン・シック・かっこいい など…) おわり GitHub Copilotでシステムプロンプトを設定することで、チーム内での一貫性のある開発体験を実現することができます。今回紹介した方法は、まだパブリックプレビュー段階ですが、今後の発展に期待が持てる機能です。ぜひ、チームの開発スタイルに合わせてカスタマイズしてみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Copilotをチーム開発で使いこなす!システムプロンプト設定方法 first appeared on SIOS Tech. Lab .
  こんにちは、サイオステクノロジーの佐藤 陽です。 今回はEntraIDのトークン構成の設定に関する内容になります。 自分が開発を進めていく中で 「トークン構成の設定でオプション要求の追加したにもかかわらず、取得されたアクセストークンの中には設定した値が入っていない」 といったケースに遭遇しました。 こちらの問題点を解消する方法や、解決策に関わる知識部分をご紹介したいと思います。 はじめに EntraID は IdP (Identity Provider) として、認証認可の仕組みを実現するために広く利用されています。 そしてEntraIDには、得られるアクセストークンやIDトークンの構成をカスタマイズできる機能があります。 この機能を利用し、とある情報をアクセストークンに含めようとしたのですが、トークンの構成を行ったにも関わらず、必要な情報が含まれないといった課題に直面しました。 そこで今回はこの原因と、原因を理解するための周辺知識について備忘録として書いていきたいと思います。 なお、今回の構成で利用する要素としては React/Nextjs MSAL(Microsoft Authentication Library) EntraID といったものになります。 とはいえ、今日のお話はEntraID以外は特に環境に依存するものではありません。 トークン構成 EntraIDでアプリケーションを作成すると、Portal上において「トークン構成」といったブレードが見られます。 選択すると オプションの要求の追加 グループ要求の追加 を行うことが可能となり、ここからトークンに含める追加情報を構成することができます。 例えば、オプション要求の追加を選択すると どのトークンに関する設定か どの要求を追加するか を設定することができます。 今回は、試しに 対象トークン 追加する要求 アクセス auth_time を設定し、アクセストークンの取得を試みます。 アクセストークンの取得 では実際にアクセストークンを取得します。 基本的はMSALのサンプルそのままを実行していきますので、詳細な実装は割愛します。 Config情報に関しては コチラ を参考に、以下の内容で設定しました。 key value clientId {EntraID上に登録したアプリケーションのクライアントID} authority https://login.microsoftonline.com/ redirectUri ローカルでの検証のため、ひとまず http://localhost:3000 scopes User.Read また、併せてEntraID上のアプリケーションの認証の設定において SPAのアプリケーションを追加し、リダイレクトURLを http://localhost としておきます。 アクセストークン内容の確認 ではこの実装に基づくアプリケーションで取得したアクセストークンの内容をDecodeしたものを一部抜粋して掲載します。 { "aud" : "00000003-0000-0000-c000-000000000000" , "iss" : "https://sts.windows.net/***/" , "iat" : 1748319065 , "nbf" : 1748319065 , "exp" : 1748322980 , "acct" : 1 , "acr" : "1" , ...(略) "xms_ftd" : "jjqoMQ6P8XOaNHnHHc-JnGBPyVrayvj9UXOQgTf1iL0BamFwYW5lYXN0LWRzbXM" , "xms_idrel" : "5 16" , "xms_st" : { "sub" : "EYjUyDvkYaYrC-pPaxE8jbp_imGQOGYEFIlRXjRG_mA" } , "xms_tcdt" : 1412690105 } すると、この中に先ほど指定したはずの auth_time の値が含まれていないことが確認できます。 なぜオプション項目が含まれていないのか 作成したアプリケーションのClientIDを正しく指定しているはずですし、設定が反映されていないのが腑に落ちません。 と、ここでDecodeした中身の aud のパラメータに注目します。 このaudのパラメータですが定義としては こちら に記載があります。 トークンの想定されている読者を識別します。 v2.0 トークンでは、この値は常に API のクライアント ID です。 v1.0 トークンでは、これは、クライアント ID、または要求で使用されるリソース URI になります。 値は、クライアントがトークンを要求した方法によって異なります。 (※今回はv2.0を利用) 現在、取得されたアクセストークンにおける aud の値は 00000003-0000-0000-c000-000000000000 です。 そしてこのaudの値はどのAPIのクライアントIDを示しているかというと、以下に示したサイトから読み解くにMicrosoftGraphのAPIとなります。 https://learn.microsoft.com/ja-jp/graph/permissions-reference https://learn.microsoft.com/ja-jp/entra/identity-platform/access-tokens つまり、 MicrosoftGraphのAPIを利用するために必要となるアクセストークンが、MicrosoftGraphのAPIを公開するアプリケーションマニフェストの情報に基づいて返されます。 マニフェストに関しては前回の記事でご紹介したので、こちらを参照ください。 【Azure】EntraIDにおけるアプリケーションマニフェストとは? 繰り返しになりますが、audの値がMicrosoftGraphのAPIのID値になっているということは そのAPIを公開するアプリケーションのマニフェストの内容に基づいてトークンの内容が決定され、返されます。 そのため、先ほど自ら設定したアプリケーションのアクセストークン構成はまったく意味を成しません。 なぜこのような状況が起きているかというと、MSALを利用する際にパラメータとして与えた Scope の設定が影響しています。 今回Scopeの設定としては、サンプルで使われていた User.Read をそのまま使ってしまっていました。 そしてこれは、MicrosoftGraphAPIの User.Read のScopeを指しています。 そのため、「このクライアントはMicrosoftGraphのAPIを利用したいんだな!」と判断されたため MicrosoftGraphのAPIを管理するアプリのマニフェストに基づいてトークンが返されてしまったのです。 これを解決するためには、自らAPIを公開し、そのAPIを利用するためのアクセストークンを発行する必要があります。 以下に実際のステップを記載します。 再取得(意図したクレームを含める方法) API の公開設定 先ほどのアプリケーションにおいて新規にAPIの公開を行います。 アプリケーションURIの発行 Scopeの追加 Scopeに関しては以下のような内容で追加します。 値としては好きなものを入力してください。 Scope の指定 MSALでアクセスする際に、今回追加したScope( api://cb769b13-3f44-41ad-abcf-444acca396af/Option.Read )にScopeを置き換えます。 こうすることで、先ほど公開したAPIにアクセスするためのアクセストークンが取得できるようになります。 export const loginRequest = { scopes : [ "api://cb769b13-3f44-41ad-abcf-444acca396af/Option.Read" ] , } ; ここまでできれば準備OKです。 トークン内容の確認 再度アクセストークンを取得し、デコードします。 { "aud" : "api://cb769b13-3f44-41ad-abcf-444acca396af" , "iss" : "https://sts.windows.net/***/" , "iat" : 1748333202 , "nbf" : 1748333202 , "exp" : 1748338754 , "acr" : "1" , "aio" : "AVQAq/8ZAAAAn3CcIgjMLmAO1FwB+2ivXeaausYBhcFitnPXL1vI7N3fh63o7WnB0jK6mH/rdhjZxTfVuRvymlC6tzAhB1disVqX7l9OhDHPF+s/qSneArw=" , "amr" : [ "pwd" ] , "appid" : "cb769b13-3f44-41ad-abcf-444acca396af" , "appidacr" : "0" , "auth_time" : 1748333497 , "email" : "ak-sato@sios.com" , "idp" : "https://sts.windows.net/47d0c615-90e7-43ad-8653-720c7bf26547/" , "ipaddr" : "123.1.7.105" , "name" : "佐藤 陽" , "oid" : "b00dbe40-3b3d-46ec-962a-4abba930da7d" , "rh" : "1.AVMA7zxCzECqM0S84dL_kXbAyhObdstEP61Bq89ESsyjlq9TAJlTAA." , "scp" : "Option.Read" , ...(略) } するとまず、 auth_time の値が正しく取得できていることが確認できました。めでたし! また、 aud の値に関しても先ほど作成したアプリケーションURIの値になっていることが分かります。 この点から、自ら作成したアプリケーションのトークン構成に紐づいてアクセストークンが返されていることがわかります。 これまでの流れを概念図に示すと以下のようになります。 Scopeとして何を指定するか audの値はどのAPIのIDとなっているか どのアプリケーションマニフェストに基づいてトークンが返ってきてるか などを意識することが重要であると考えました。 IDTokenの場合は? なお、IDトークンに関しては特にこういった設定を行わなくてもトークンの構成の設定は反映されます。 これはIDトークンの性質を考えれば分かりますが、IDTokenはAPIのアクセスに利用されるものではないためです。 そのためAPIに関わる設定に依らず、アプリケーション自体の設定に依存するため、今回のようなAPIの設定は不要となります。 まとめ 今回は、アクセストークンに自ら指定したオプション要求が含まれていない課題の解決方法および、その周辺知識の紹介を行いました。 aud のパラメータの定義 アプリケーションマニフェストの考え方 アクセストークン・IDトークンの役割 などがしっかり理解できていればすぐ分かることでしたが、なかなかそこに気づけず時間を溶かしてしまいました。 このあたりまだまだ理解が不十分なところもあるのでしっかり抑えていきたいと思います。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【Azure】EntraIDのトークン構成でオプション要求が反映されない問題の解決法 first appeared on SIOS Tech. Lab .
今号では、cron によるタスクの定期実行について、その仕組みや設定方法について説明します! cron とは cron とは、 指定した時間、曜日、日付に自動でコマンドを実行してくれる デーモンの名称です。 デフォルトでもいくつかの操作が cron ジョブ により実行されるようになっており、具体的な実行内容などは設定ファイルに記述されています。 (例:logrotate の実行、特定パッケージの自動更新など) cron デーモンが常駐し、毎分設定ファイルをチェックして「 実行時間が来たか? 」を確認しています。 cron の設定ファイル、ディレクトリの配置場所 cron は下記のファイル、ディレクトリにて設定ファイルが配置されます。 まずはファイルやディレクトリの種類、役割について見ていきましょう。 (※今回は RHEL8 の環境を前提に説明します) 1. /etc/crontab システム全体の cron ジョブを設定するためのファイル。 システム管理者 (root) のみが編集できます。 (※ 通常は、このファイルを直接編集しません) 2. /etc/cron.d システム全体の cron ジョブを設定するためのファイル。 /etc/crontab ではなく、このディレクトリ配下にファイルを配置することが一般的です。 3. /etc/cron.hourly 毎時 (1時間ごと) 実行される設定ファイル (スクリプト) を配置するディレクトリ。 4. /etc/cron.daily 毎日 (1日ごと) 実行される設定ファイル (スクリプト) を配置するディレクトリ。 5. /etc/cron.weekly 毎週 (1週間ごと) 実行される設定ファイル (スクリプト) を配置するディレクトリ。 6. /etc/cron.monthly 毎月 (1ヵ月ごと) 実行される設定ファイル (スクリプト) を配置するディレクトリ。 7. /var/spool/cron ユーザごとの個別の設定ファイル (crontab) を配置するディレクトリ。 後述する crontab コマンド でタスクを作成すると、このディレクトリ配下に設定ファイルが作成されます。 cron ジョブの設定方法 (時間指定) cron によるタスクを作成するには、下記の 2通りの方法があります。 crontab コマンドで設定する (ユーザごとの個別の設定) /etc/cron.d 配下に設定ファイルを配置する (システム全体の設定) それぞれの手順を説明します。 crontab コマンドで設定する (ユーザごとの個別の設定) 各ユーザごとに、現在どのような cron のタスクが設定されているかを確認するには crontab -l コマンドを実行します。 デフォルトでは何も登録されていないため、下記のような表示になります。 $ crontab -l no crontab for ykaino cron のタスクを追加、編集するには crontab -e コマンドを実行します。 テキストエディタが開きます。 cron は、 分、時、日、月、曜日 の 5つのフィールドで実行タイミングを指定します。 * * * * * コマンド 例1:毎日 0時に実行: 0 0 * * * 例2:毎週月曜日の朝 6時に実行: 0 6 * * 1 例3:毎分実行: * * * * * 例えば、決まった時間に特定のスクリプトを動作させたい場合、下記のように設定し、保存 ([:w]、もしくは[ZZ]) します。 30 7 * * * /path/to/myscript.sh ※スクリプトは絶対パスで指定しておくと確実です。 なお、 crontab -e でタスクを追加後 crontab -l を再度実行してみると、下記のようにタスクが追加されていることが分かります。 $ crontab -l 30 7 * * * /path/to/myscript.sh /etc/cron.d 配下に設定ファイルを配置する (システム全体の設定) システム全体に適用されるタスクを追加したい場合、crontab コマンドではなく /etc/cron.d 配下に直接ファイルを作成します。 タスクの設定方法は、crontab コマンドで実施した方法と同じです。 例えば、上の例でも出した myscript.sh をシステム全体で適用したい場合、下記のように設定します。 # cat /etc/cron.d/myscript 30 7 * * * /path/to/myscript.sh なお、設定ファイル追加後は cron を再起動しなくてもタスクが適用されます。 cron ジョブの設定方法 (スクリプト) cron のタスクは、時間指定する方法だけでなくスクリプト形式でも登録することができます。 単純なコマンド実行だけでなく、処理を分岐させたい場合や、より複雑な処理が必要な場合はスクリプト形式での登録が有用です。 例として、デフォルトで配置されている /etc/cron.daily/logrotate の内容を見てみます。 1 #!/bin/sh 2 3 /usr/sbin/logrotate /etc/logrotate.conf 4 EXITVALUE=$? 5 if [ $EXITVALUE != 0 ]; then 6 /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" 7 fi 8 exit $EXITVALUE 3行目 logrotate コマンド (/usr/sbin/logrotate) が logrotate の設定ファイル (/etc/logrotate.conf) を読み込みます。 4行目 直前に実行されたコマンド (ここでは logrotate コマンド) の 終了ステータス を EXITVALUE に格納します。 5~7行目 EXITVALUE が 0 以外 (つまり logrotate がエラーで終了) の場合、ログにエラーを示す旨のメッセージを書き込む処理を実行します。 8行目 スクリプトの終了ステータスを EXITVALUE と同じ値に設定します。 次号について 次号では、 cron タスクを追加する際の tips や、 スクリプト形式のタスク についてもう少し詳しく説明します! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!cron によるタスク管理1 first appeared on SIOS Tech. Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 今後リリース予定の Linux 6.15 カーネルをもって、「i486」と初期「Pentium」プロセッサのサポートが終了することになりました。 「Linux」で「i486」と初期「Pentium」のサポートが終了へ https://japan.zdnet.com/article/35232760/ マイクロソフトは、Microsoft Azure 上で動作する新たなディストリビューション「Azure Image Testing for Linux」のサービス提供を発表しました。 マイクロソフト、「Azure Image Testing for Linux」をサービス提供 https://japan.zdnet.com/article/35232982/ Google Chrome の最新バージョン「Chrome 137」に関する情報が公開されました。深刻度「High」の脆弱性が修正対応されています。 「Google Chrome」に8件の脆弱性、最大深刻度は「High」 https://forest.watch.impress.co.jp/docs/news/2016522.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年5月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 SaaS 型のデータストリーミングプラットフォームの Confluent で、自然言語にて Confluent Cloud を操作できる Confluent MCP (Model Context Protocol) サーバーが GitHub に公開されました。この MCP サーバーを MCP ホストの Claude に組み込んで使ってみたので、設定方法を共有したいと思います。 confluentinc / mcp-confluent 何ができるかは、私たちが普段話す言葉で Claude から Confluent Cloud を操作するデモンストレーションをご覧ください。 本資料では、既に Confluent Cloud を利用されている方を対象としており、Confluent MCP サーバーを MCP ホストに設定する手順に焦点を当てて解説します。従って、Confluent Cloud の利用開始手順や設定、およびデータストリーミングプラットフォームの利用方法など、MCP サーバーの設定以外の内容については、本資料の範囲外とさせていただきます。 また、MCP についても既にネット上でたくさんのコンテンツが親切に説明されているので、本資料の範囲外とさせて頂きます。個人的には、KDDIアジャイル開発センター 御田さんが書かれた以下資料がお薦めです。 やさしいMCP入門 | 著者:御田 稔さま なお、ハンズオンで利用する機能は Claude の無料プランでお試し頂くことが可能ですが、便利さを感じて頂いたら是非ともアップグレードもご検討ください。 構成概要 筆者が動かした際の主な構成要素は以下の通りです。 Windows 10 Professional Claude desktop for Windows version 0.9.3 Windows PowerShell 5.1.19041.5737 Node.js v22.15.0 ハンズオンを構成する環境は以下の通りです。 MCP ホストは、Claude desktop for Windows のネイティブアプリ版を利用します。 MCP サーバーの構成における主な考慮事項は以下の通りです。 Confluent MCP サーバーは TypeScript で実装されているため、MCP サーバーの稼働環境は Node.js を導入します。 GitHub のリポジトリから取得した TypeScript のソースコードを JavaScript へトランスパイルして、Node.js 環境で MCP サーバーを実行します。 実行に当たっては、Confluent Cloud から API Key、API Secret などの情報を事前に収集します。 基礎環境の構築 Claude desktop for Windows インストール MCP ホストに Web 版ではなくアプリ版の Claude desktop for Windows を利用するため、以下公式手順を参考に Windows PC へインストールします。 デスクトップ版Claudeのインストール | Anthropicヘルプセンター Node.js インストール JavaScript の実行環境が必要なため、以下手順を参考に Windows PC へ Node.js をインストールします。 初期環境構築: Node.js on Windows Confluent Cloud から情報収集 本章では Confluent Cloud にログインして MCP サーバーの動作に必要な各種情報を収集します。 Confluent Cloud API を利用して Confluent Cloud を操作するため、API Key、および API Secret を取得します。 利用している Confluent Cloud の環境情報を収集します。 Confluent Cloud へログイン Confluent Cloud のログインページにアクセスしログインします。 Confluent Cloud Confluent Cloud にログインできたら、次の章に進みます。 「.env」ファイルの設定情報 Confluent MCP サーバーの起動には「 .env 」ファイルの設定が不可欠で、設定値について以下 GitHub のドキュメントに記載がありますが、多くの設定項目があるため、私自身も情報の収集には苦労しました。これまでに得たノウハウに基づき、ファイル作成に必要な項目の設定値や取得方法について解説します。 confluentinc/mcp-confluent Confluent MCP サーバーの環境構築する過程で、「 「.env」ファイルの作成 」でファイルを作成する際にこの解説を参考にしてください。 API_KEY / API_SECRET 「CONFLUENT_CLOUD_API_KEY」を始めとする各種 API Key と API Secret の取得に当たり、画面右上のハンバーガーメニューから「API Keys」を選択します。 API Key とペアーで API Secret を新たに発行するために、「+ Add API key」ボタンをクリックします。 自分自身のアカウントとして振る舞う API Key を発行するので「My account」を選び「Next」ボタンをクリックし、次ページ以降は発行する API Key に準じた章に従って進めます。 API Key と API Secret の発行画面では、画面を閉じると API Secret は二度と確認できなくなるため、必ずメモを取って控えます。控え終わったら「Complete」ボタンをクリックして発行プロセスを完了し、控えた API Key と API Secret はそれぞれの「 .env 」へ設定します。 CONFLUENT_CLOUD_API_KEY / API_SECRET 「Cloud resource management」を選択して「Next」ボタンをクリックします。 「Name」に任意の API Key 名を入力して「Next」ボタンをクリックして API Key と API Secret を発行します。 FLINK_API_KEY / API_SECRET 「Flink region」を選択して、MCP ホストから操作したい Flink の「Environment」「Cloud provide」「Region」を選択して「Next」ボタンをクリックします。 「Name」に任意の API Key 名を入力して「Next」ボタンをクリックして API Key と API Secret を発行します。 KAFKA_API_KEY / API_SECRET 「Kafka cluster」を選択して、MCP ホストから操作したい Kafka の「Environment」「Cluster」を選択して「Next」ボタンをクリックします。 「Name」に任意の API Key 名を入力して「Next」ボタンをクリックして API Key と API Secret を発行します。 SCHEMA_REGISTRY_API_KEY / API_SECRET 「Schema Registry」を選択して、MCP ホストから操作したい「Environment」「Schema Registry」を選択して「Next」ボタンをクリックします。 「Name」に任意の API Key 名を入力して「Next」ボタンをクリックして API Key と API Secret を発行します。 HTTP_HOST / HTTP_PORT HTTP_HOST と HTTP_PORT には、デフォルトの「 localhost 」「 3000 」を「 .env 」へ設定します。 BOOTSTRAP_SERVERS 画面の左ペインのメニューから「Environments」を選択し、MCP ホストから操作する Environment を選びます。 Cluster 一覧で MCP ホストから操作する Cluster を選びます。 画面の左ペインのメニューから「Cluster overview > Cluster settings」を選択し、Endpoints にある「Bootstrap server」の値を「 .env 」へ設定します。 CONFLUENT_CLOUD_REST_ENDPOINT 「 https://api.confluent.cloud 」を「 .env 」へ設定します。 KAFKA_ENV_ID MCP ホストから操作する Environment が選ばれている状態で、画面の右ペインの Environment details にある「ID」の値を「 .env 」へ設定します。 KAFKA_CLUSTER_ID MCP ホストから操作する Environment が選ばれている状態で、Cluster 一覧で MCP ホストから操作する Cluster を選びます。 画面の右ペインの情報一覧にある「Cluster ID」の値を「 .env 」へ設定します。 KAFKA_REST_ENDPOINT MCP ホストから操作する Cluster が選ばれている状態で、画面の左ペインのメニューから「Cluster overview > Cluster settings」を選択し、Endpoints にある「REST endpoint」の値を「 .env 」へ設定します。 FLINK_ORG_ID 画面右上のハンバーガーメニューから「Organization settings」を選択します。 Details にある「Organization ID」の値を「 .env 」へ設定します。 FLINK_COMPUTE_POOL_ID MCP ホストから操作する Environment が選ばれている状態で、画面の左ペインのメニューから「Flink」を選択し、MCP ホストから操作する Compute pool から「ID」の値を「 .env 」へ設定します。 FLINK_ENV_ID / FLINK_ENV_NAME Flink の Environment は、Kafka の Environment の ID を意味するため「 KAFKA_ENV_ID 」と同じ値を「 .env 」へ設定します。なお、FLINK_ENV_NAME には kafka の Environment に名付けられた名前を設定します。 FLINK_DATABASE_NAME Flink のデータベース名は、Kafka の Cluster ID を意味するため「 KAFKA_CLUSTER_ID 」と同じ値を「 .env 」へ設定します。 FLINK_REST_ENDPOINT MCP ホストから操作する Environment が選ばれている状態で、画面の左ペインのメニューから「Flink」を選択し、「Compute pools」タブを選択すると表示されている Compute pool 情報にある「Cloud & region」から cloud と region の値を控えます。(画面例では、「cloud = aws」「region = ap-northeast-1」が該当します) 「Endpoints」タブを選択し、Public Endpoints にある「Public endpoint」に書かれている書式を控えます。 控えた値、書式とプロトコルの「https://」使って得られたアドレスを「 .evn 」へ設定します 画面例では次のアドレスになります。:https://flink.ap-northeast-1.aws.confluent.cloud SCHEMA_REGISTRY_ENDPOINT MCP ホストから操作する Environment が選ばれている状態で、画面の左ペインのメニューから「Stream Governance > Schema Registry」を選択し、「Overview」タブを選択すると表示されている Endpoints にある「Public endpoint」の値を「 .env 」へ設定します。 Confluent MCP サーバーの環境設定 MCP サーバーの設定 Confluent MCP サーバーの GitHub リポジトリを取得して、MCP ホストの Claude と連携できるように準備します。 GitHub からリポジトリのダウンロード 「ドキュメント」フォルダ配下に、リポジトリを保存して管理する「 devlopment 」フォルダを作成します。 Path: C:\Users\{ユーザー名}\Documents\development\ {ユーザー名} はご利用中 PC のユーザー名に置き換えてください ブラウザで GitHub の「Confluent MCP Server」のリポジトリにアクセスします。 confluentinc/mcp-confluent リポジトリを ZIP ファイルで取得するため、緑色の「Code」ボタンをクリックすると表示するメニューから「Download ZIP」を選択して、名前を付けて保存するダイアログが表示されたら保存するフォルダを指定してダウンロードを開始します。 エクスプローラーでダウンロードした「 mcp-confluent-main.zip 」を右クリックして表示するメニューから「すべて展開…」を選択して ZIP ファイルを展開します。 ZIP ファイルから展開された「 mcp-confluent-main 」フォルダを、事前に準備した「 C:\Users\{ユーザー名}\Documents\development\ 」フォルダに移動するため、右クリックで表示するメニューの「切り取り」を選択して切り取ります。 なお、展開後のフォルダ構成が「 …\Donwloads\mcp-confluent-main\mcp-confluent-main\… 」のように「 mcp-confluent-main 」フォルダが二重に展開されている場合は、下段の「 mcp-confluent-main 」フォルダを移動対象にします。 事前に準備した「 C:\Users\{ユーザー名}\Documents\development\ 」フォルダをアクティブにして、右クリックのメニューから「貼り付け」を選択して「 mcp-confluent-main 」を移動します。 フォルダ名を「 mcp-confluent-main 」から末尾の main を消し「 mcp-confluent 」に変更します。 「 C:\Users\{ユーザー名}\Documents\development\mcp-confluent 」フォルダが準備できました。 Git コマンドがインストールされている PC 環境では、Windows PowerShell を使い以下コマンドでリポジトリを取得して頂くことで問題ありません。 PS C:\Users\...\development> git clone https://github.com/confluentinc/mcp-confluent.git 「.env」ファイルの作成 Confluent MCP サーバーの「 C:\Users\{ユーザー名}\Documents\development\mcp-confluent 」フォルダに「.env」ファイルを新規に作成します。 ファイルに設定する内容は以下 GitHub のドキュメントを確認してください。 confluentinc/mcp-confluent ファイル作成に必要な項目の設定値や取得方法について以下でも解説しています。 「.env」ファイルの設定情報 依存パッケージのインストールとトランスパイル Windows のスタートメニュー「W」セクションから「Windows PowerShell」を選択して起動します。 Window PowerShell ではコマンドラインベースで操作を行います。 リポジトリを保存したフォルダへ遷移します。 PS C:\Users\...> cd C:\Users\{ユーザー名}\Documents\development\mcp-confluent\ ソースコードが実行時に必要な依存パッケージをインストールします。 PS C:\Users\...\mcp-confluent> npm install added 726 packages, and audited 727 packages in 36s 127 packages are looking for funding run `npm fund` for details found 0 vulnerabilities 依存パッケージが格納された「 node_modules 」フォルダが新たに出来上がったことを確認します。 PS C:\Users\...\mcp-confluent> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2025/05/19 12:20 node_modules <--- 出来上がっている d----- 2025/05/19 12:15 src : ビルドして TypeScript のソースコードから Node.js で実行できる JavaScript のソースコードへトランスパイルします。 PS C:\Users\...\mcp-confluent> npm run build > @confluentinc/mcp-confluent@1.0.2 build > tsc && tsc-alias ビルド先の「 dist 」フォルダが新たに出来上がり、MCP サーバーの起動を担う「 dist\index.js 」が出来上がったことを確認します。 PS C:\Users\...\mcp-confluent> ls dist\ Mode LastWriteTime Length Name ---- ------------- ------ ---- : -a---- 2025/05/19 12:22 6036 index.js <--- 出来上がっている : Claude(MCP ホスト)の設定 Confluent MCP サーバーが Claude で利用できるように設定します。 連携先 MCP サーバーの設定 Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面左上のハンバーガーメニューから「ファイル > 設定…」を選択します。 Confluent MCP サーバーが利用できるようにするために、「開発者」タブの「構成を編集」ボタンをクリックします。 エクスプローラーが立ち上がり、Claude の設定ファイルに該当する「 claude_desktop_config.json 」ファイルをダブルクリックしてエディターで開きます。 「 claude_desktop_config.json 」ファイルに Confluent MCP サーバーを利用するための設定を記載して保存します。 { "mcpServers": { "confluent": { "command": "node", "args": [ "C:\\Users\\{ユーザー名}\\Documents\\development\\mcp-confluent\\dist\\index.js", "--env-file", "C:\\Users\\{ユーザー名}\\Documents\\development\\mcp-confluent\\.env" ] } } } 「 claude_desktop_config.json 」ファイルの編集における主な考慮事項は以下の通りです。 「mcpServers > confluent > args」フィールドで主な考慮事項は以下の通りです。 node コマンドが MCP サーバーとして起動する「 index.js 」ファイルの Path を指定します。 「 --env-file 」オプションに付与する値は「 .env 」ファイルの Path を指定します。 「 .env 」ファイルは「 「.env」ファイルの設定情報 」章を参考に設定します。 Path にある「 {ユーザー名} 」は PC で利用しているユーザー名に置き換えます。 Path でフォルダの区切りを示す「\」は二重の「\\」で書く必要があります。 Claude の再起動で設定反映 変更した設定ファイルの適用には、ウィンドウの「×」ボタンで終了させるだけではなく、Claude のプロセスを完全に終了させる必要があるため、Windows タスクバーの画面右側のアイコン表示領域から Claude アイコン(トゲトゲのウニの様なデザイン)を探し、右クリックで表示されるメニューから「終了」を選択してプロセスを完全に終了させます。 Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面中央のテキストボックス内の左下にある「検索とツール」アイコンをクリックします。表示されたメニューに、「confluent」の項目と右横に数字が表示されていたら、Confluent MCP サーバーは正常に設定できました。 Confluent MCP サーバーのデモ 私たちが普段話す言葉で Claude から Confluent Cloud を操作するデモンストレーションをご覧ください。 Confluentに新たに「mcp_users」トピックを作ってください。 Confluentに以下コネクターを新規に作成して、既に存在する「mcp_users」へテストデータを流してください。 – コネクター名: Connector_mcp_users – プラグイン: Sample Data – レコード書式: JSON_SR – スキーマ: Users なお、最終確認は自分でやるので、できあがり確認はしなくてよいです。 Confluentに「mcp_users」と同じスキーマ構造で新たに「mcp_users_mask」トピックを作り、Flinkを使って「mcp_users」トピックの全てのデータを流すSQLを発行してください。 但し、「gender」フィールドだけは登録されている半角英数字を正規表現で「*」へ置換してマスクしてください。 なお、最終確認は自分でやるので、できあがり確認はしなくてよいです。 まとめ Confluent MCP サーバーの組み込みとデモンストレーションはいかがでしたでしょうか。Confluent に限らず、ミドルウェアベンダー各社から設定を簡略化する MCP サーバーが提供されていますので、ぜひ色々と試してみていただければと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post MCP を使って 自然言語で Confluent Cloud を操ってみた first appeared on SIOS Tech. Lab .
こんにちは。サイオステクノロジーの久保です。 突然ですが、ご自身のウェブサイトが現在どのような状況にあるのか、把握できていますか? どのページがよく閲覧されているのか、どこから訪問者が来ているのか、どのようなユーザーが関心を寄せているのか…。 これらを明らかにするのが「 アクセス解析 」です。 アクセス解析とは、ウェブサイトを訪れたユーザーの行動データを収集・分析することで、現状の把握や課題の発見、改善のための手がかりを得るための手法です。 アクセス解析でわかること たとえば、アクセス解析を行うことで、以下のような情報を得ることができます。 訪問者数 :どのくらいのユーザーがサイトを訪れているのか ページごとの閲覧数 :人気ページやあまり見られていないページの把握 流入元の特定 :検索エンジン、SNS、他サイトからのリンクなど、訪問のきっかけ ユーザー属性 :年齢層や地域など(※個人が特定できない範囲) ユーザー行動 :滞在時間、離脱ページ、ページ遷移など コンバージョン :購入やお問い合わせなど、目的の達成状況 こうした情報をもとに、ウェブサイトの改善策を検討したり、効果的なマーケティング戦略を立てたりすることが可能になります。 アクセス解析ツールの選定(Google Analytics) アクセス解析を行うためには、ツールの導入が必要です。 現在では、無料・有料を問わずさまざまなアクセス解析ツールが提供されています。 中でも多くのウェブサイトで採用されているのが、 Google Analytics(GA4) です。アカウントを作成すればすぐに使い始められる手軽さから、多くの人に選ばれてきました。 しかし近年、以下のような理由から別のツールを検討するケースも増えています。 仕様変更への戸惑い :GA4では、解析の軸がセッションからユーザーへと移り、従来のようなページ中心の分析がしづらくなった 予告なしのアップデート :仕様や操作画面の変更が突発的に行われることがある データのサンプリング :大量データを扱う際、すべてではなく一部のみを基にした分析となる場合があり、精緻な分析が難しい 保存期間の制限 :データの保存期間を自由に設定できない プライバシーへの懸念 :収集されたデータが大手プラットフォームのビッグデータとして利用されることへの不安 こうした課題を背景に、「もっと自由にデータを管理したい」「プライバシーを重視したい」「柔軟に拡張したい」という声が高まっています。 新たな選択肢「Matomo(マトモ)」 そのような中で注目を集めているのが Matomo というアクセス解析ツールです。 キャプション:Matomo demo画面 Matomoの概要 Matomoは LAMPサーバー(Linux, Apache, MySQL, PHP) 上で動作するオープンソースの無料ツールです。 2010年に「Piwik」として誕生し、2018年に「Matomo」へ名称変更。2025年5月時点で最新版は Matomo 5.2.3 です。 現在、190か国・100万以上のウェブサイト(国連やアムネスティなどの国際機関を含む)で採用され、その信頼性が認められています。 Matomoの主な特徴 Matomoが多くのユーザーに支持されるのには、明確な理由があります。ここでは、その主な特徴を詳しく見ていきましょう。 データの完全所有権  すべての分析データはユーザー自身が用意するデータベースにのみ保存されます。  外部プラットフォームに共有されることはなく、安心して活用できます。 プライバシー保護に配慮   GDPR(EU)、HIPAA(米国)、CCPA(カリフォルニア)、LGPD(ブラジル)、PECR(英国) など  世界各国の厳格なプライバシー法に準拠する設定が可能です。 拡張可能な機能  有償・無償のプラグインがあり、より高度なレポー確認ト作成や自社要件に応じた機能拡張ができます。 無料のオープンソース  ライセンス費用は不要。ソフトウェアの実行・共有・調査・変更が自由で、コストを抑えながら高機能環境を構築できます。 柔軟性と高いカスタマイズ性  250以上の設定項目があり、多くはデフォルトで対応可能。  さらにHTTP APIを活用してカスタムレポートの自動作成もできます。 データサンプリングなし  大規模解析の場合でも、GAのような推測に基づくサンプリングは行わず、「すべてのデータ」で正確なレポートを作成できます。 GDPR対応支援   データ匿名化   トラッキングのオプトアウト提供   EU圏内でのデータ保存  違反時に最大年間収益4%の罰金リスクがあるGDPR対応も支援します。 アクセスログファイル解析も可能  トラッキングコードを設置できない場合でも、Apache/nginx/IISなど主要サーバーのログファイルを活用した解析が可能。  過去データも遡って分析できる点が特徴です(ただし一部指標は対応不可)。 より深くアクセス解析とMatomoを知るために もしアクセス解析とMatomoに興味を持ち、さらに詳しく知りたい場合は サイオステクノロジー Financial & Unique SI Service Line が発信する Note記事 をぜひご覧ください。 筆者の私自身、以下の記事を活用して「 ウェブ解析士 」の資格を取得しました! 「初心者のためのやさしいアクセス解析入門 第1章」 https://note.com/sti_fusl/m/me4d2bc51e095 「初心者のためのやさしいアクセス解析入門 第2章」 https://note.com/sti_fusl/m/mb94d1a928bde 「Matomoな日々」 https://note.com/sti_fusl/m/m4ba37fa0eb27 サイオステクノロジーのMatomoサポートサービス また、サイオステクノロジーでは2014年からMatomo国内向けテクニカルサポートサービスを提供しています。 https://sios.jp/lp/matomo/ このサービスを通じて、Matomoの導入支援や運用に関する専門的なサポートを受けることができます。 Matomoは、データの所有権、プライバシー保護、そして高機能な分析機能を兼ね備えた、ウェブサイト運営者にとって強力な味方となるでしょう。ぜひこの機会に、Matomoの導入を検討してみてはいかがでしょうか。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post アクセス解析、やるならこれ!初心者にもわかりやすいMatomoのすすめ first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの佐藤 陽です。 今回は少しニッチなところで、EntraIDのアプリケーションマニフェストの概要をご紹介したいと思います。 本当に記事にしたいのはこのマニフェスト部分関連でハマったポイントの解決方法なのですが その解決方法を理解するにはマニフェストの概要を知っておく必要があるため、まずはこちらを紹介していきたいと思います。 はじめに Azureを利用したアプリケーションにおいて認証認可の仕組みを実現する際、EntraIDを使うことも多いかと思います。 そのEntraIDには「マニフェスト」と呼ばれる設定項目があります。 今回そのマニフェストに関してどういったものかをご紹介したいと思います。 EntraIDのアプリケーションマニフェストとは 認証認可の仕組みを実現する際には、EntraID上でアプリケーションを作成し、Microsoft Authentication Library (MSAL) などを利用することが多いかと思います。 MSALを利用することで、ClientIDなどを与えるだけで簡単にログイン画面などの実装が可能となります。 今回触れる「アプリケーションマニフェスト」は、そのEntraID上に作成したアプリケーションの要素になります。 実際、アプリケーションのページを表示すると、管理のブレードの一番下に マニフェスト というブレードが存在します。 そしてこの項目を選択すると、何やらjson形式のテキストデータが表示されることがわかるかと思います。 これが今回ご紹介するマニフェストの内容になります。 どういった内容が記載されているのか マニフェストのテキストを確認すると、何かの設定パラメータのようにも見えます。 実は、このマニフェストにはアプリケーションの設定にかかわる全てのことが記載されています。 つまり、Azure Portal上において認証の設定や、トークン構成などを行うことが多いかと思いますが、 それらの設定内容がすべてこのマニフェストにテキストとして反映されます。 (逆に、マニフェストを修正することでしか設定できない項目もいくつか存在します。) 例えば以下のようなものが含まれます。 Property 内容 appId アプリケーションのクライアントID groupMembershipClaims ユーザーが所属するグループ情報をクレームに含めるか、およびそのグループの種別の設定 signInAudience サインインできるユーザーのテナント(単一テナントorマルチテナントなど) 設定を変更する際には、AzurePortal上から設定してもらえればこのマニフェストの値も書き換わりますし、一方でマニフェストを直接書き換えることでもPortal上の設定を変更することが可能です。 修正方法 マニフェストの情報を直接Azure Portalから修正することはできません。 修正するためには、一度ダウンロードし、ローカルで修正したのちにアップロードします。 試しに一度ダウンロードし、空欄であった”identifierUris”の値を以下のように設定します。 "identifierUris": [ "api://058cae36-9e85-4e29-b9e1-658943948f40" ], そしてアップロード後にAPIの公開のブレードを確認すると 新規にアプリケーションID URIが発行されていることが確認できました。 では試しに、変更してはいけなさそうな値を変えてみたいと思います。 アプリケーション作成時に自動で割り当てられる appId の値を修正し、アップロードします。 するとさすがに以下のように怒られてしまいました。 確かに ドキュメント を読んでも、appIdに関しては It’s a not nullable and read-only attribute. との記載があるので修正はできないようです。 マニフェストの使われ方 このマニフェストがアプリケーション設定そのものだということは理解いただけたかと思います。 MSALなどでこのアプリケーションに対してアクセスする際は、このマニフェストの内容に基づいて処理が行われます。 例えば、クライアントに返されるアクセストークンのクレームの項目などがそれに該当します。 そのため、認証認可の仕組みを実現する際には「どこのアプリケーションマニフェストに基づいて処理が行われているか」という意識を持つことが大切です。 まとめ 今回はEntraIDのアプリケーションにおけるマニフェストという概念をご紹介しました。 一言でいえば、EntraIDのすべての設定項目をjson形式のオブジェクトであらわしたものになります。 そしてこのマニフェストに沿って認証認可の処理が実現されます。 なぜこのマニフェストについてご紹介したか、ですが 最近EntraID周りの実装でハマったポイントがあり、それを解消するためにこのマニフェストの概念の理解が必要でした。 そのため次回はそのハマりポイントの解消方法についてご紹介します。 ではまた! 参考ページ https://learn.microsoft.com/en-us/entra/identity-platform/reference-microsoft-graph-app-manifest ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【Azure】EntraIDにおけるアプリケーションマニフェストとは? first appeared on SIOS Tech. Lab .
サイオステクノロジーの橋本 です。 個人的に max_wal_size と wal_keep_size の役割を忘れたり、混同しがちなので、 備忘のためブログを書きます。 パラメータの説明 max_wal_size PostgreSQL 9.5 から追加されているパラメータです。 役割は 2 つあります。 ・保存される WAL ファイルサイズの上限値設定 (ソフトリミット) ・CHECKPOINT の実行条件の制御 wal_keep_size PostgreSQL 13から登場したレプリケーション用のパラメータです。 以前は wal_keep_segments という名前でした。 削除も再利用もされることがなく保存が保証される WAL ファイルサイズを定義します。 チューニング時の指針について max_wal_size max_wal_size の重要な役割として CHECKPOINT の実行条件の制御があります。 ※CHECKPOINT の仕組みは複雑で説明すると脱線するので触れません  CHECKPOINT が発生すると大きな DISK I/O が発生します。 CHECKPOINT が発生する条件は以下の 2 つです (or 条件) ・ checkpoint_timeout 秒が経過する ・max_wal_size に達する CHECKPOINT の実行は大きな DISK I/O が伴うため可能な限り発生を抑止したいです。 例えば checkpoint_timeout = 60min と設定すれば CHECKPOINT は一時間に一回の実行に抑えることが可能です。 ※CHECKPOINT の発生間隔が長くなるとクラッシュリカバリに要する時間が長くなるデメリットがあります。 この場合、CHECKPOINT は一時間に一回の実行としたいという思惑があるわけです。 次にようやく max_wal_size のチューニング観点です。 CHECKPOINT は max_wal_size で指定したサイズ分だけ WAL ファイルが生成されても実行されます。 一時間に 10 GB の更新 (WAL ファイルの生成) がされるシステムは 最低限 max_wal_size の値も 10 GB と設定する必要があります。 ただし、厳密に max_wal_size で指定したサイズ分の更新があった場合に CHECKPOINT 発生するわけではなく 多少の誤差が生じます。 この点は運用していく中でチューニングが必要です。 wal_keep_size このパラメータは最低限の保存する WAL ファイルサイズを指定します。 主にストリーミングレプリケーションの時に必要となるパラメータです。 例えばネットワークメンテナンスやパッチ適用などでレプリケーションが最長 3 時間途絶える可能性がある環境があるとします。 3 時間で生成される WAL ファイルサイズを wal_keep_size を指定する必要があります。 つまり一時間に 10 GB の更新 (WAL ファイルの生成) がされるシステムは 30 GB を wal_keep_size に指定する必要があります。 wal_keep_size の値は max_wal_size の値より大きく手も小さくても問題ありません。 以下の場合は WAL ファイルは最低 30 GB を保持してくれます。 max_wal_size = 10GB wal_keep_size = 30GB この例からもわかるように max_wal_size はあくまでソフトリミットとなります。 max_wal_size を超えて WAL ファイルを保持する例として他には以下があります。  archive_command の失敗  レプリケーションスロットに基づく保存 ただし、レプリケーションスロットを利用している場合は wal_keep_size を設定する必要は薄くなります。 本記事がチューニングを検討しているかたの助けになれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PostgreSQLのWAL管理 max_wal_sizeとwal_keep_sizeの役割とチューニング first appeared on SIOS Tech. Lab .