機雷がなんだ! 全速前進!

SEというかプログラマというか、日々のエンジニア生活の中で体験したことなどを中心に書き残しています。

【再掲・2019年】偉人たちの足跡を辿って - シリコンバレー訪問レポート

※本記事は 2019年12月に私が執筆 したレポートを、内容を変えずに再公開したものです。
 別サイトに掲載していましたが閉鎖され閲覧できなくなったためこちらに再掲載しました。

 

ラスベガスで開催されるre:Invent 2019開催前日の12月1日(日)の朝、我々はサンフランシスコ国際空港に降り立ちました。目的はシリコンバレーを訪れるためです。昨年re:Invent 2018へ参加したメンバーは「Amazonのパワーをもっともっと感じようではないか!」という理由からAmazon本拠地であるシアトルを訪れましたが、今年は米国ITベンチャー発祥の地であるシリコンバレーを見て気持ちを高めてからラスベガスで開催されるre:Invent 2019へ行くことにしました。

 

コンピュータ歴史博物館

最初に訪れたのがこちらコンピュータ歴史博物館です。米国だけでなく全世界のコンピュータの歴史を代表する品々がここに展示されています。
 

(コンピュータ歴史博物館のエントランス)

 
館内に展示されている古今東西のコンピュータに関する展示物それぞれを解説すると、それだけで何百枚もの写真と原稿になってしまうので、ここでは私が見て感動した2つのものについて少しだけ解説するにとどめたいと思います。
 
(暗号化装置エニグマ)
 
まずは、かつてドイツ軍で使用されていた解読不可能と言われた暗号化装置エニグマです。映画「イミテーション・ゲーム」や「U-571」、古くは「U・ボート」などに登場する、歴史的に有名な暗号化装置です。名前だけでもご存知の方は多いのではないでしょうか?映画はフィクションも含まれていますが、このエニグマを巡ってかつて様々な戦いやドラマが繰り広げられたのです。天才数学者アラン・チューリングらが暗号解読機ボンベ(bombe)を開発して、暗号を解読できるようになって戦局は大きく変わっていきました。これを目の前にするとかつての天才たちがしのぎを削った歴史を感じずにはいられません。
 
(The Mythical Man-month:人月の神話)
 
そしてこちらはフレデリック・ブルックスの著書「人月の神話(The Mythical Man-month)」です。最初の刊行が1975年なのですが、当時のものでしょうか。初版から既に44数年が経っているにも関わらず、この本は今でも尚、大規模開発プロジェクトにおけるソフトウェア工学の古典として読み継がれている名著です。本の横に書かれているのは、あの有名な『ブルックスの法則』で「遅れているソフトウェアプロジェクトへの要員追加は、さらにプロジェクトを遅らせるだけだ(Adding manpower to a late software project makes it later.)」です。日進月歩のコンピュータ業界において、40年を経ても名著として読み続けれているというの本当に驚きですが、逆に考えると、テクノロジーが進化しても大規模プロジェクトにおいて我々が直面する課題の本質というのはあまり変わっていないのかもしれません。改めてこの教訓を胸に刻んでおきたいと思いました。
 

グーグルプレックス

コンピュータ歴史博物館から徒歩で行ける距離にグーグルプレックス(Googleplex)があります。グーグルプレックスはGoogle本社の愛称です。その道すがら工事中の巨大なスタジアムのような建造物を見ましたが、2019年完成予定だった新社屋「Google Campus Charleston East」のようです。完成が遅れているのでしょうか。完成すると新たに2700人を収容できる巨大でモダンな新オフィスになるそうです。
 
(グーグルプレックス:Google本社)
 
いよいよ到着したGoogle本社です。この日が日曜日だったせいか、人も車もほとんどおらず閑散としていました。会社というより有名私立大学のキャンパスといった印象です。敷地内のあちこちにベンチやリクライニングチェアが設置してありました。この日は生憎の雨模様だったので座りませんでしたが、天気が良ければPCを抱えて外で気持ちよくcodingできそうな雰囲気です。
 
(グーグルプレックスのT-Rex化石標本)
 
何故か敷地内に恐竜の化石標本が展示されていました。この恐竜の名前はスタン(Stan)と言うらしいです。創業者達が近くのスタンフォード大学出身だからなのか、巨体故に環境に適応できずに絶滅してしまったことを反面教師にするためなのか真相は不明のようです。Google社の名前の由来がgoogol(10の100乗)に由来する、というのと同じくらい謎めいていますね。

 

(グーグルプレックスのドロイド君Pie)
 
Google社は2005年にAndroid社を買収した訳ですが、以来Androidのバージョンにはスイーツな開発コードネームが付けられてきました。写真のPieはAndroid 9.0のものです。現在最新のAndroidのバージョンはAndroid 10.0ですが、何故まだ古いバージョンのドロイド君が展示されたままなのか?というと実は悲しいかなAndroid 10.0からはGoogleは開発コードネームを廃止してしまったのです。それ故まだ旧バージョンのドロイド君が展示されたままなのだと思うのですが、彼がいつまで美味しそうなPieに囲まれたままで居られるのかは分かりません。この写真を撮影したのは2019年12月1日ですが、もしかしたらとても貴重な写真になるかもしれませんね。
 

カルトレイン(Caltrain)

我々が宿泊したホテルの最寄駅はサン・アントニオ駅なのですが、鉄道(Caltrain)に乗って3駅先がパロアルト駅です。せっかくシリコンバレーに来たので、地元の鉄道に乗車してパロアルトまで移動してみることにしました。
 
(Caltrainの乗車チケット自動販売機)
 
Caltrainの駅には改札もゲートも何もありません。ホームに乗車チケットを販売している自動販売機がありました。ですが、買い方が全然分かりません(汗)日本では出発~到着駅で値段が決まるので駅名が分かっていれば乗車チケットが買えますし、駅員さんに聞いてチケットを購入することもできます。ですが、ホームには駅員さんも居らず、自動販売機はゾーン(?)を指定してチケットを買うという謎仕様で、ゾーンの概念が分かってないと迂闊に手を出せません。ググってみたところ、スマホアプリでもチケットが買えるということが分かったのですぐにGoogle Playからダウンロードしてインストールしました。

 

(Caltrainのスマホアプリ)
 
このアプリを使えば、駅名をFROM-TOで入力するだけで簡単に乗車チケットが購入できます。支払いもGoogle Payなのでキャッシュレスで簡単です。もしCaltrainに乗車するならスマホアプリで購入することをオススメします。注意点としては、土日だったこともあってかこの鉄道1時間に1本くらいしか走っていないので、うっかり1本乗り逃すと次までかなり待たされることになるので気をつけましょう。

 

(Caltrainの車両)
 
Caltrainは全線が非電化で動力はディーゼル機関のようです。架線がないため景観はなかなか良いです。各駅停車の客車列車は2階建ての立派な車両でなんと日本車輌製だそうです。せっかくなのでこの客車列車の2階の座席に座って景色を眺めながら移動しました。また、Caltrainは自転車で通学、通勤する人も多いためか客車列車の他に自転車や大きい荷物を格納する車両もありました。駅のホームの中まで自転車で移動してきて、そのまま列車内に持ち込んでる人もいました。なかなか日本では味わえない感覚です。うーん、ダイナミック。こういうの好きです。 

 

(Caltrainパロアルト駅)
 

パロアルト周辺

いよいよパロアルト駅に到着しました。パロアルト駅はすぐ隣のスタンフォード駅と1km程と近くてすぐに歩ける距離です。スタンフォード大学は全米きっての名門大学であり、シリコンバレーで生まれた多くのスタートアップ企業の創始者達の母校でもあります。名前を出せばキリがないですが、あのGoogle、Hewlett-Packard、Yahoo、Sun Microsystems、Cisco、Netflixなどの名だたる企業を世に送り出した名門中の名門大学なのです。
 

(Hewlett-Packardガレージ)

 
パロアルト駅から15分程歩いた住宅街の一角に「Hewlett-Packardガレージ」があります。ここは今からおよそ80年前の1938年にHP(Hewlett-Packard)の創始者であるウィリアム・ヒューレットとデビッド・パッカードがこの場所を拠点にして創業したという場所で、言わばシリコンバレーの歴史が始まった発祥の地と言える場所です。ようやく辿り着いた時にはすっかり日も暮れ夜になっていたのでHPガレージに続く門は閉められてしまっていましたが、夜でも記念碑とガレージはライトアップされていて何とか見ることができました。わざわざ歩いて来た甲斐がありました。
 
(トヨタ・リサーチ・インスティテュート)
 
パロアルトからホテルへ帰る途中にトヨタ・リサーチ・インスティテュート(Toyota Research Institute, Inc.、略称:TRI)がありました。人工知能技術に関する先端研究、商品企画を目的として、2016年にトヨタ自動車により設立された研究所で、最近では自動運転のテレビCMでも時々見かけますね。日本を代表するモノづくりメーカー企業も、今後の生き残りを賭けて、米国の大学(MITやスタンフォード)と連携して最先端の研究開発を行っており、その拠点をシリコンバレーに置いているようです。

 

(Adobe Creek:アドビ川)
 
翌日12月2日(月)の朝、宿泊したホテルの周りを散歩していたら「Adobe Creek」の表示を見つけました。「Adobe」の名前が気になったので調べてみたところ、なんとあのPhotoshopやIllustratorで有名なAdobe Systemsの社名の由来となった「Adobe川」でした。創業者ジョン・E・ワーノック(John・E・Warnock)の自宅の裏をこの川が流れていたことから社名がAdobe Systemsになったんだとか。少しウロつくだけで、こういう面白いエピソードに出会えるからシリコンバレーは本当に面白いですね。
 
(アルタ・メサ記念公園:ジョブズのお墓)
 
Adobe川を遡って歩いて行くとアルタ・メサ記念公園があります。ここにはApple社の共同設立者スティーブ・ジョブズの遺体が埋葬されています。不思議なことに彼の墓石はどこにもありませんでした。その理由は分かりませんが、墓石で場所が分かってしまうと彼の墓を荒らす不届きな輩が居るかもしれませんので、むしろ良かったのかもしれません。ホテルからこのアルタ・メサ記念公園へ向かう道中は、平日(月曜日)の朝の通勤時間帯ということもあり、前日と比べて交通量も人も多くて賑わいがありましたが、この門を通り抜けた先は不思議な静寂に包まれていました。公園内の木々にはたくさんのリス達が居て、敷地内を走り回っていました。安らかに眠るにはとても良い場所だと思いました。
 
(アルタ・メサ記念公園:ホテルへの帰り道)
 
ホテルへの帰路、真っ赤に紅葉した木々や歩道に散った木の葉を見ると、まるで自分が秋の日本に居るような気持ちになりました。そうえば、ジョブズは日本の禅に傾倒していたそうですね。ふと2005年にスティーブ・ジョブズがスタンフォード大学の卒業式に招待され「点と点を繋ぐ(Connecting The Dots)」あの有名なスピーチをしたことを思い出しました。スピーチの最後は「ハングリーたれ。愚かたれ(Stay hungry, Stay foolish)」で締め括られます。その意味については人それぞれの解釈があるようですが色々と考えさせられる言葉だと思います。わたしはジョブズ信仰者ではないですが、彼のスピーチやプロダクトに徹底的に拘る姿勢、その生き様からは凄まじい情熱を感じます。彼は当時特別なものだったコンピュータを我々にとって身近なもの(Macintosh、Pixar、iPhone)に変えてしまった大変革者なのです。アルタ・メサは、既に故人となった彼について思い出し、そして、彼が言ったこと、やったことについて考える機会になりました。
 
(シリコンバレーでのえげつない量の朝食)
 
散歩から戻りラスベガスへ出発する朝の集合時間になって、近所のレストランへ皆で朝食を食べに行きました。メニューを見てもよく分からないので、適当に朝食セット的なものと温かいコーヒーを注文したところ、とんでもない量の朝食が出てきました。皆、ヒーヒー言いながら食べましたが、一緒に行ったメンバーの中には食べきれず残す人も。隣の席の(地元の?)おばあちゃんが同じ量をペロッと食べ切ってる姿を見て皆で苦笑いしました。シリコンバレーお婆ちゃん、恐るべし。朝食の値段はチップ込で21米ドル(約2300円)。高いですよね(汗)。シリコンバレーは一見すると素朴で田舎っぽい雰囲気ですが、とても物価が高い(この後行くラスベガスはもっと高かったけど...)のです。
ともあれ、これでラスベガスへ向かう心と体の準備が整いました!
さあ、いざ飛行機でラスベガスへ!待ってろよAWS re:Invent 2019!
 

※本記事は当時の体験を記録したものであり、現在の状況とは異なる場合があります。

生成AIで誰でも簡単に物理シミュレーションできる時代

時々 Google Colaboratory を使って遊んでいます。Webブラウザだけで簡単に計算したりシミュレーション結果を可視化したりできて非常に便利です。しかも基本的に無料で利用できます。以前の記事で紹介した通り、生成AI(Google Gemini)を使うことでサクッとプログラムの修正ができてしまうことを知ってからは、試してみたいけどちょっと面倒だと思ったことを、生成AIで簡単に試せることを知りました。

例えば、以下のような事がものの数分で簡単に試せてしまいます。なんて恵まれた時代だ。

例1:特殊相対性理論の可視化

プロンプト:

アインシュタインの特殊相対性理論の一般式(速度がゼロでない場合)
を光速は秒速30万km/sとして二次元のグラフで可視化してください。
縦軸を質量、横軸を速度としてください。また、グラフのラベルは英語表記としてください。

実行結果:

import numpy as np
import matplotlib.pyplot as plt

# Speed of light in km/s
c = 299792.458

# Rest mass (arbitrary value for demonstration)
m0 = 1.0  # Assume a rest mass of 1 unit

# Define a range of velocities from 0 up to (but not including) the speed of light
# We'll use a small epsilon value to avoid division by zero at v = c
v = np.linspace(0, c * 0.995, 500)  # velocities from 0 to 99.5% of c

# Calculate the relativistic mass
# The formula is m = m0 / sqrt(1 - (v^2 / c^2))
mass = m0 / np.sqrt(1 - (v**2 / c**2))

# Create the plot
plt.figure(figsize=(10, 6))
plt.plot(v, mass)

# Set labels and title
plt.xlabel('Velocity (km/s)')
plt.ylabel('Relativistic Mass (arbitrary units)')
plt.title('Relativistic Mass vs. Velocity')
plt.grid(True)

# Show the plot
plt.show()

この結果から、例えば、物体が光速の99.5%に達した場合、質量が約10倍程度になることが分かります。 また、さらにそれ以上に速度を上げようとすると質量は急激に増加してしまい、結局は光速に到達できないことも一目で分かります。 ブルーバックスなんかを読んでいると、本に記載されている内容を無性に試したり、確認してみたいという衝動に駆られた事が何度もありました。 しかし、その度に、それだけのためにいちいコード書いて試すのが面倒で思いとどまる事が多かったですが、今ではこんなに簡単にできてしまいます。

例2:プログラムの解説(要約)

もちろん解説や要約やレビューも生成AIにはお手の物です。

プロンプト:

このプログラムを解説してください。

実行結果:

このコードは、Albert Einstein特殊相対性理論(Special Relativity) における「相対論的質量」の式を使って、速度と質量の関係をグラフ化するものです。順番に解説します。

① 使っている理論式

特殊相対性理論では、物体が光速に近づくと質量が増加すると表されます(※現在の物理では“相対論的質量”よりも“エネルギー増加”で説明するのが主流ですが、この式は教育目的でよく使われます)。

相対論的質量の式

  • ( m_0 ):静止質量(rest mass)
  • ( v ):物体の速度
  • ( c ):光速
  • 分母の部分を ローレンツ因子(Lorentz factor) と呼びます

② コードの各部分の意味

1️⃣ ライブラリの読み込み

import numpy as np
import matplotlib.pyplot as plt
  • numpy → 数値計算
  • matplotlib → グラフ描画

2️⃣ 光速の設定

c = 299792.458

3️⃣ 静止質量

m0 = 1.0

デモ用に「1」としています。 単位は任意(arbitrary units)。

4️⃣ 速度の範囲

v = np.linspace(0, c * 0.995, 500)
  • 0 から 99.5% の光速まで
  • 500点で分割

なぜ 100% にしない?

だと

になり、ゼロ割りで無限大になるからです。

5️⃣ 相対論的質量の計算

mass = m0 / np.sqrt(1 - (v**2 / c**2))

式そのままです。

  • 低速ではほぼ m0
  • 光速に近づくと急激に増加
  • c に近づくと無限大へ発散

6️⃣ グラフ描画

plt.plot(v, mass)
横軸

Velocity (km/s)

縦軸

Relativistic Mass

③ グラフの物理的意味

このグラフは次の性質を示します:

🔹 低速領域
  • 質量はほぼ一定
  • ニュートン力学とほぼ同じ
🔹 中速領域
  • 少しずつ増加
🔹 光速に近づくと
  • 急激に上昇(ほぼ垂直に立ち上がる)

つまり:

物体は光速に到達できない

なぜなら、 到達するには「無限大のエネルギー」が必要だからです。

④ このコードの物理的ポイント

✔ 特殊相対性理論の一般式を使用 ✔ 速度がゼロでない場合を含む ✔ ローレンツ因子を利用 ✔ 発散(infinite behavior)を数値的に確認できる

⑤ 現代物理での注意

現在の物理では、

「質量が増える」という言い方よりも、

として

  • 質量は不変
  • エネルギーが増加する

と説明するのが一般的です。

⑥ まとめ

このコードは:

✔ 特殊相対性理論の相対論的質量を ✔ 数値的に計算し ✔ 光速に近づくと発散する様子を ✔ 英語ラベル付きで可視化する

教育用として非常にわかりやすい例です。


(↑↑↑ 生成AIの実行結果はここまで ↑↑↑)


こんな感じで壁打ち的に生成AIと会話しながら、次はこれ、その次は... と、どんどん試していけるのがいいですよね。

おまけ

(当たり前ですが)数学の数値シミュレーションも簡単に試せます。

プロンプト:

マンデルブロ集合を数値計算して可視化してください。

実行結果:

pip install numpy pandas matplotlib seaborn scikit-learn
import numpy as np
import matplotlib.pyplot as plt

def mandelbrot(c, max_iter):
    z = 0
    for n in range(max_iter):
        z = z*z + c
        if abs(z) > 2:
            return n
    return max_iter

width, height = 512, 512
max_iter = 256

x, y = np.linspace(-2, 1, width), np.linspace(-1.5, 1.5, height)
c = x[np.newaxis, :] + y[:, np.newaxis] * 1j

mandelbrot_set = np.vectorize(mandelbrot)(c, max_iter)

plt.figure(figsize=(12, 12))
plt.imshow(mandelbrot_set, extent=[-2, 1, -1.5, 1.5], cmap='viridis')
plt.xlabel("Re(c)")
plt.ylabel("Im(c)")
plt.title("Mandelbrot Set")
plt.colorbar()
plt.show()

コラッツ予想のシミュレーションなんかさせるのも楽しいですね(もちろん解決はできませんが...)。

参考

github.com

Microsoft Entra ID (旧 Azure AD) をIdPとしてOIDC認証する際の注意点(バックエンド)

前回記事では主にMicrosoft Entra ID (旧 Azure AD)側の設定とフロントエンド(SPA)を中心にした内容でしたが、今回はバックエンド側の注意点について記載します。

本記事では、バックエンドはJava(Spring Boot 3.5.8)で実装したWebAPIを想定しています。

変更前IdP:Amazon Cognito

Amazon CognitoをIdPとして以下のようなエンドポイントを指定していました。

バックエンドの設定

以下のように設定ファイル(application.yml)でエンドポイントを指定しました。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxxxx

この設定で特に問題なくOIDC認証ができていました。

変更後IdP:Microsoft Entra ID (旧 Azure AD)

前提

  • Microsoft Entra ID (旧 Azure AD)側でIdP設定済
  • フロントエンドはReactで実装したSPAを想定

今回前提とする設定内容は前回記事の【最終版】と同様です。(詳細はそちらをご覧ください)

問題

Microsoft Entra ID (旧 Azure AD)をIdPに変更したところ、フロントエンドからバックエンドAPI呼び出しで以下の401エラーが発生してしまいました。

Www-Authenticate:

Bearer error="invalid_token", 
error_description="The aud claim is not valid", 
error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"

これは、Spring SecurityのOAuth2リソースサーバが、受信したJWTトークン(アクセストークン)のaudクレームを検証した際、想定する値と一致しないため認証が失敗たことを示しています。

手順①:バックエンドの設定

以下のように設定ファイル(application.yml)でエンドポイントを指定していました。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
          audiences:
            - api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b

手順②:実行結果

アクセストークンを確認してみるとiss(issuer)は一致しているようです。(以下✅のところ) しかし、aud(audience)が一完全には致していないことが分かりました。(以下❌のところ)

{
  "aud": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b", ← ❌ 完全一致してない(設定ファイルには「api://」がある)
  "iss": "https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0", ← ✅ 完全一致している
:
(以下略)

解決方法

前述の設定ファイルとアクセストークンの差異を修正します。

手順①:バックエンドの設定

以下のように設定ファイル(application.yml)でエンドポイントを修正しました。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
          audiences:
            - 4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b

手順②:実行結果

アクセストークンを確認してみるとiss(issuer)とaud(audience)が一致していることが確認できました。(以下✅のところ)

{
  "aud": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b", ← ✅ 完全一致している
  "iss": "https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0", ← ✅ 完全一致している
:
(以下略)

今回のポイント

前回記事ではフロントエンドの設定でaud(audience)を指定する際、以下のように「api://」というプレフィックスを記述していました。

VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

しかしバックエンドの設定ファイル(application.yml)では

          audiences:
            - api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b ← ❌ 「api://」は不要

ではなく、以下のようにプレフィックスなしで記述する必要があるので注意が必要です。

          audiences:
            - 4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b ← ✅ 「api://」なしが正解

また、aud(audience)は複数形の「audiences」でありリスト型式で記述します。(以下参照)

          audiences:
            - my-api-audience
            - another-allowed-audience

アクセストークンに含まれる'aud'クレームがこのリストのいずれかと一致するか検証されるという仕組みになっているようです。

参考

Microsoft Entra ID (旧 Azure AD) をIdPとしてOIDC認証する際の注意点(フロントエンド)

AWS CognitoでOIDC認証していたWebアプリケーション(SPA)のIdPをMicrosoft Entra ID (旧 Azure AD) に変更した時に遭遇した注意点について記載します。

本記事に記載した注意点を実際に動かして確認したい場合は、以下のサンプルアプリ(Reactで実装されたSPA)を使えば簡単に試すことができます。

github.com

変更前IdP:Amazon Cognito

Amazon CognitoをIdPとして以下のようなエンドポイントを指定していました。

フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxxxx
VITE_OIDC_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email

この設定で特に問題なくOIDC認証ができていました。

変更後IdP:Microsoft Entra ID (旧 Azure AD)

Microsoft Entra ID (旧 Azure AD)をIdPに変更したところいくつかの問題に遭遇しました。

遭遇1:アクセストークンがInvalid Signature (無効な署名)になる

手順①:Microsoft Entra ID でアプリを新規登録

Azure ポータルのMicrosoft Entra ID で以下のようにアプリを登録しました。

概要

管理 -> Authentication (Preview):リダイレクトURIの構成

管理 -> API のアクセス許可

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email

手順③:実行結果

取得したアクセストークンの署名検証結果がInvalid Signature(無効な署名)になってしまいました。 アクセストークンを確認してみると audience(aud)がMicrosoft Graphになっていました。(以下❌のところ)

{
  "aud": "00000003-0000-0000-c000-000000000000", ← ❌ Microsoft Graph になっている
  "iss": "https://sts.windows.net/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/",
  "iat": 1771046123,
  "nbf": 1771046123,
  "exp": 1771051527,
  "acct": 0,
  "acr": "1",
  "acrs": [
    "p1"
  ],
  "aio": "xxxxx",
  "altsecid": "1:live.com:XXXXXXXXXXXXXXXX",
  "amr": [
    "pwd",
    "mfa"
  ],
  "app_displayname": "web-app-sample",
  "appid": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "appidacr": "0",
  "email": "xxxxxxxxxx@gmail.com",
  "family_name": "山田",
  "given_name": "一郎",
  "idp": "live.com",
  "idtyp": "user",
  "ipaddr": "xxx.xxx.xxx.xxx",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "platf": "3",
  "puid": "1003200378EAFE7B",
  "rh": "xxxxx",
  "scp": "email User.Read profile openid",
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx5f5cab",
  "signin_state": [
    "kmsi"
  ],
  "sub": "xxxxx",
  "tenant_region_scope": "JPR",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "unique_name": "live.com#xxxxxxxxxx@gmail.com",
  "uti": "xxxxx",
  "ver": "1.0",
  "wids": [
    "62e9xxxx-xxxx-xxxx-xxxx-xxxxxx145e10",
    "b79fxxxx-xxxx-xxxx-xxxx-xxxxxxe85509"
  ],
  "xms_acd": xxxxx,
  "xms_act_fct": "X X",
  "xms_ftd": "xxxxx"
  "xms_idrel": "X XX",
  "xms_st": {
    "sub": "xxxxx"
  },
  "xms_sub_fct": "X XX",
  "xms_tcdt": xxxxx,
  "xms_tnt_fct": "X XX"
}

これは以下の記事と同じことが発生しており Microsoft Entra IDとフロントエンドの両方で設定の見直しが必要です。

Microsoft ID プラットフォームのアクセストークンが”Invalid Signature”になる件 その1:Express + Passport編 | 株式会社アイオス


遭遇2:アクセストークンのバージョンが想定外(v1.0 トークン)

手順①:Microsoft Entra ID でアプリに設定追加

Azure ポータルのMicrosoft Entra ID で以下のようにアプリ設定を追加しました。

管理 -> API の公開

【↓】スコープ名は「web-app-backend」としています。

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

手順③:実行結果

今回は得したアクセストークンの署名検証結果がSignature Verified(署名確認済み)になってしまいました。 アクセストークンを確認してみると audience(aud)やscope(scp)が登録したアプリのものに変更されていました。(以下✅のところ) しかしフロントエンドの設定でエンドポイントは2.0を指定したのに、iss(issuer)やアクセストークンのver(version)が「1.0」となっています。(上記❌のところ)

{
  "aud": "api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b", ← ✅ 登録したアプリになっている
  "iss": "https://sts.windows.net/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/", ← ❌ v1.0 のエンドポイント
  "iat": 1771043342,
  "nbf": 1771043342,
  "exp": 1771048647,
  "acr": "1",
  "aio": "xxxxx",
  "amr": [
    "pwd",
    "mfa"
  ],
  "appid": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "appidacr": "0",
  "email": "xxxxxxxxxx@gmail.com",
  "family_name": "山田",
  "given_name": "一郎",
  "idp": "live.com",
  "ipaddr": "xxx.xxx.xxx.xxx",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "rh": "xxxxx",
  "scp": "web-app-backend", ← ✅ 登録したアプリになっている
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx3fb9da",
  "sub": "xxxxx",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "unique_name": "live.com#xxxxxxxxxx@gmail.com",
  "uti": "xxxxx",
  "ver": "1.0", ← ❌ v1.0 トークン
  "xms_ftd": "xxxxx"
}

最終版:アクセストークンのバージョンを是正(v2.0 トークンへ)

手順①:Microsoft Entra ID でアプリの設定変更

Azure ポータルのMicrosoft Entra ID で以下のようにアプリ設定を変更しました。

管理 -> マニフェスト

変更前: "accessTokenAcceptedVersion": null,  ← v1.0 トークンを発行(デフォルト値)
変更後: "accessTokenAcceptedVersion": 2,     ← v2.0 トークンを発行

手順②:フロントエンドの設定

以下のようなエンドポイントを指定しました。(※問題2と同様のため変更不要です)

VITE_OIDC_AUTHORITY=https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0
VITE_OIDC_CLIENT_ID=4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b
VITE_OIDC_REDIRECT_URI=http://localhost:5173
VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

手順③:実行結果

アクセストークンを確認してみるとちゃんとiss(issuer)やver(version)が「2.0」になっていました。(以下✅のところ)

{
  "aud": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "iss": "https://login.microsoftonline.com/4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb/v2.0", ← ✅ v2.0 のエンドポイント
  "iat": 1771043595,
  "nbf": 1771043595,
  "exp": 1771048913,
  "aio": "xxxxx",
  "azp": "4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b",
  "azpacr": "0",
  "idp": "live.com",
  "name": "山田 一郎",
  "oid": "0bf5xxxx-xxxx-xxxx-xxxx-xxxxxxd0f6a6",
  "preferred_username": "xxxxxxxxxx@gmail.com",
  "rh": "xxxxx",
  "scp": "web-app-backend",
  "sid": "0020xxxx-xxxx-xxxx-xxxx-xxxxxx5f5cab",
  "sub": "xxxxx",
  "tid": "4647xxxx-xxxx-xxxx-xxxx-xxxxxx394ecb",
  "uti": "xxxxx",
  "ver": "2.0", ← ✅ v2.0 トークンになっている
  "xms_ftd": "xxxxx"
}

補足

上記の例ではフロントエンドの設定でscope(scp)を以下のように指定しましたが、末尾の .default というのがワイルドカード的な意味(全て)になっています。

VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/.default

より厳密に制御する必要がある場合は、以下のように許可するスコープ名を個別に指定するようにします。

VITE_OIDC_SCOPE=openid profile email api://4f82xxxx-xxxx-xxxx-xxxx-xxxxxx5fa07b/web-app-backend

ただし今回の例では、そもそもスコープが1つしか定義されていないため、いずれもアクセストークンのscope(scp)は同じ内容になります。

おわりに

当初は、各IdPともにOIDCという標準的な共通のルールに則って実装されているだろうからフロントエンドのエンドポイントの向き先を変更するだけでサクッと変更できるものと考えていましたが、実際にやってみると各IdPごとに違いがあり、クライアント側だけでなくIdP側も仕様を理解して正確に設定しないと期待した通り動作してくれませんでした。もちろん知っていれば非常に簡単な設定ではあるのですが、当初の認識の甘さも相まって期待した挙動になるまでに、何度も試行錯誤することになりました。このあたりの見通しの甘さは反省が必要ですね。

VSCodeでエディタ文字サイズ変更後にサクッと元に戻す方法

VSCodeで画面共有するときなど、一時的にエディタ文字サイズを変更したいことがあります。その方法と、その後サクッと元に戻すための小技メモです。

エディタ文字サイズ(のみ)を変更する方法

一時的にエディタ文字サイズ(のみ)を変更したい場合は、VSCodeの設定で「Editor: Mouse Wheel Zoom」をONにします。

この設定だけでChromeなどと同じノリ(「Ctrl」+ホイール)で簡単にエディタ文字サイズ(のみ)を変更できます。

<初期状態>


<拡大状態>

おっきくて見やすいですね

エディタ文字サイズ(のみ)を元に戻す方法

性格的な問題かもしれませんが、用が済んだら初期状態に戻さないと気持ち悪いので、その方法についても記載しておきます。残念ながら現状ではVSCodeの設定変更だけでは簡単に実現できなそうだったので拡張機能「Zoom Bar」をインストールしました。

この拡張機能をインストールするとVSCodeの画面の下側にステータスが表示されるようになります。この中で以下の「エディターのフォントのズームをリセット」と言うボタンをクリックすると一発で初期状態のフォントサイズに戻すことができます。

これでもう気持ち悪くない

k8s でコアAPIか否かを確認する方法(kubectl api-resources)

k8s で以下の ClusterRoles などを定義する際に、対象リソースのAPI Groupが何か確認したい場合があります。(以下★★★のところ)

例(ClusterRoleのマニフェスト)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: storage-admin
rules:
- apiGroups: [""]
  resources: ["persistentvolumes"]
  verbs: ["get", "watch", "list", "create", "delete"]
- apiGroups: ["storage.k8s.io"] ← ★★★これを知りたい★★★
  resources: ["storageclasses"]
  verbs: ["get", "watch", "list", "create", "delete"]

そんな時は、以下のコマンドでサクッと確認できます。

kubectl api-resources

実行すると以下のようなリストが表示されます。

NAME                                SHORTNAMES   APIVERSION                          NAMESPACED   KIND
bindings                                         v1                                  true         Binding
componentstatuses                   cs           v1                                  false        ComponentStatus
configmaps                          cm           v1                                  true         ConfigMap
endpoints                           ep           v1                                  true         Endpoints
events                              ev           v1                                  true         Event
limitranges                         limits       v1                                  true         LimitRange
namespaces                          ns           v1                                  false        Namespace
nodes                               no           v1                                  false        Node
persistentvolumeclaims              pvc          v1                                  true         PersistentVolumeClaim
persistentvolumes                   pv           v1                                  false        PersistentVolume
pods                                po           v1                                  true         Pod
podtemplates                                     v1                                  true         PodTemplate
replicationcontrollers              rc           v1                                  true         ReplicationController
resourcequotas                      quota        v1                                  true         ResourceQuota
secrets                                          v1                                  true         Secret
serviceaccounts                     sa           v1                                  true         ServiceAccount
services                            svc          v1                                  true         Service
mutatingwebhookconfigurations                    admissionregistration.k8s.io/v1     false        MutatingWebhookConfiguration
validatingadmissionpolicies                      admissionregistration.k8s.io/v1     false        ValidatingAdmissionPolicy
validatingadmissionpolicybindings                admissionregistration.k8s.io/v1     false        ValidatingAdmissionPolicyBinding
validatingwebhookconfigurations                  admissionregistration.k8s.io/v1     false        ValidatingWebhookConfiguration
customresourcedefinitions           crd,crds     apiextensions.k8s.io/v1             false        CustomResourceDefinition
apiservices                                      apiregistration.k8s.io/v1           false        APIService
controllerrevisions                              apps/v1                             true         ControllerRevision
daemonsets                          ds           apps/v1                             true         DaemonSet
deployments                         deploy       apps/v1                             true         Deployment
replicasets                         rs           apps/v1                             true         ReplicaSet
statefulsets                        sts          apps/v1                             true         StatefulSet
selfsubjectreviews                               authentication.k8s.io/v1            false        SelfSubjectReview
tokenreviews                                     authentication.k8s.io/v1            false        TokenReview
localsubjectaccessreviews                        authorization.k8s.io/v1             true         LocalSubjectAccessReview
selfsubjectaccessreviews                         authorization.k8s.io/v1             false        SelfSubjectAccessReview
selfsubjectrulesreviews                          authorization.k8s.io/v1             false        SelfSubjectRulesReview
subjectaccessreviews                             authorization.k8s.io/v1             false        SubjectAccessReview
horizontalpodautoscalers            hpa          autoscaling/v2                      true         HorizontalPodAutoscaler
cronjobs                            cj           batch/v1                            true         CronJob
jobs                                             batch/v1                            true         Job
certificatesigningrequests          csr          certificates.k8s.io/v1              false        CertificateSigningRequest
leases                                           coordination.k8s.io/v1              true         Lease
endpointslices                                   discovery.k8s.io/v1                 true         EndpointSlice
events                              ev           events.k8s.io/v1                    true         Event
flowschemas                                      flowcontrol.apiserver.k8s.io/v1     false        FlowSchema
prioritylevelconfigurations                      flowcontrol.apiserver.k8s.io/v1     false        PriorityLevelConfiguration
gatewayclasses                      gc           gateway.networking.k8s.io/v1        false        GatewayClass
gateways                            gtw          gateway.networking.k8s.io/v1        true         Gateway
grpcroutes                                       gateway.networking.k8s.io/v1        true         GRPCRoute
httproutes                                       gateway.networking.k8s.io/v1        true         HTTPRoute
referencegrants                     refgrant     gateway.networking.k8s.io/v1beta1   true         ReferenceGrant
helmchartconfigs                                 helm.cattle.io/v1                   true         HelmChartConfig
helmcharts                                       helm.cattle.io/v1                   true         HelmChart
accesscontrolpolicies                            hub.traefik.io/v1alpha1             false        AccessControlPolicy
aiservices                                       hub.traefik.io/v1alpha1             true         AIService
apiaccesses                                      hub.traefik.io/v1alpha1             true         APIAccess
apibundles                                       hub.traefik.io/v1alpha1             true         APIBundle
apicatalogitems                                  hub.traefik.io/v1alpha1             true         APICatalogItem
apiplans                                         hub.traefik.io/v1alpha1             true         APIPlan
apiportals                                       hub.traefik.io/v1alpha1             true         APIPortal
apiratelimits                                    hub.traefik.io/v1alpha1             true         APIRateLimit
apis                                             hub.traefik.io/v1alpha1             true         API
apiversions                                      hub.traefik.io/v1alpha1             true         APIVersion
managedsubscriptions                             hub.traefik.io/v1alpha1             true         ManagedSubscription
addons                                           k3s.cattle.io/v1                    true         Addon
etcdsnapshotfiles                                k3s.cattle.io/v1                    false        ETCDSnapshotFile
nodes                                            metrics.k8s.io/v1beta1              false        NodeMetrics
pods                                             metrics.k8s.io/v1beta1              true         PodMetrics
ingressclasses                                   networking.k8s.io/v1                false        IngressClass
ingresses                           ing          networking.k8s.io/v1                true         Ingress
ipaddresses                         ip           networking.k8s.io/v1                false        IPAddress
networkpolicies                     netpol       networking.k8s.io/v1                true         NetworkPolicy
servicecidrs                                     networking.k8s.io/v1                false        ServiceCIDR
runtimeclasses                                   node.k8s.io/v1                      false        RuntimeClass
poddisruptionbudgets                pdb          policy/v1                           true         PodDisruptionBudget
clusterrolebindings                              rbac.authorization.k8s.io/v1        false        ClusterRoleBinding
clusterroles                                     rbac.authorization.k8s.io/v1        false        ClusterRole
rolebindings                                     rbac.authorization.k8s.io/v1        true         RoleBinding
roles                                            rbac.authorization.k8s.io/v1        true         Role
deviceclasses                                    resource.k8s.io/v1                  false        DeviceClass
resourceclaims                                   resource.k8s.io/v1                  true         ResourceClaim
resourceclaimtemplates                           resource.k8s.io/v1                  true         ResourceClaimTemplate
resourceslices                                   resource.k8s.io/v1                  false        ResourceSlice
priorityclasses                     pc           scheduling.k8s.io/v1                false        PriorityClass
csidrivers                                       storage.k8s.io/v1                   false        CSIDriver
csinodes                                         storage.k8s.io/v1                   false        CSINode
csistoragecapacities                             storage.k8s.io/v1                   true         CSIStorageCapacity
storageclasses                      sc           storage.k8s.io/v1                   false        StorageClass
volumeattachments                                storage.k8s.io/v1                   false        VolumeAttachment
volumeattributesclasses             vac          storage.k8s.io/v1                   false        VolumeAttributesClass
ingressroutes                                    traefik.io/v1alpha1                 true         IngressRoute
ingressroutetcps                                 traefik.io/v1alpha1                 true         IngressRouteTCP
ingressrouteudps                                 traefik.io/v1alpha1                 true         IngressRouteUDP
middlewares                                      traefik.io/v1alpha1                 true         Middleware
middlewaretcps                                   traefik.io/v1alpha1                 true         MiddlewareTCP
serverstransports                                traefik.io/v1alpha1                 true         ServersTransport
serverstransporttcps                             traefik.io/v1alpha1                 true         ServersTransportTCP
tlsoptions                                       traefik.io/v1alpha1                 true         TLSOption
tlsstores                                        traefik.io/v1alpha1                 true         TLSStore
traefikservices                                  traefik.io/v1alpha1                 true         TraefikService

これらの項目(列)のうち APIVERSION を確認するだけです。

ざっと表にまとめると以下のような感じです。

APIVERSION の例 API Group(apigroup) コアAPI? 説明
v1 (空=core API) YES Pod / Service など。
apiGroups: [""] を使う
apps/v1 apps NO Deployment / DaemonSet など
batch/v1 batch NO Job / CronJob など
storage.k8s.io/v1 storage.k8s.io NO StorageClass / CSI Driver など
rbac.authorization.k8s.io/v1 rbac.authorization.k8s.io NO ClusterRole / ClusterRoleBinding など
apiextensions.k8s.io/v1 apiextensions.k8s.io NO CustomResourceDefinition (CRD)
networking.k8s.io/v1 networking.k8s.io NO Ingress / NetworkPolicy など

つまり、

  • APIVERSION が v1 だけ → コア API
    → RBAC では apiGroups: [""]

  • APIVERSION が <group名>/v1 の形式 → 非コア API
    → RBAC では apiGroups: ["<group名>"]

ということです。

一度知ってしまえば簡単ですね。

補足

kubectl のバージョン で kubectl api-resources の結果が以下のように異なります。

1.26 以前の場合(古い)

NAME   SHORTNAMES   APIGROUP       APIVERSION   …

1.27以降の場合(今回)

NAME   SHORTNAMES   APIVERSION     …

nano エディタこわい問題

最近k8s系のオペレーションで一時的にマニフェストファイルを編集しようとするとテキストエディタとしてnanoがデフォルトのエディタとして起動してくることがあり見慣れないUIで一瞬焦ります。もちろんデフォルトエディタは変更できますが、その場でちょっとした編集をしたいだけの時は、それも面倒です。これまでは基本的にviVim)を利用することが多かったですが、最低限の操作を覚えておくのが良さそうです。

画面表示

画面下に常にコマンド一覧が表示される。

例:

 
^G Get Help ^O Write Out ^X Exit
  • ^Ctrl キーを意味する

  • ^XCtrl + X

基本操作

操作 nano vimでの感覚
入力 そのまま打つ i してから入力
保存 Ctrl + O → Enter :w
終了 Ctrl + X :q
保存して終了 Ctrl + O → Enter → Ctrl + X :wq
検索 Ctrl + W /
カット(行削除) Ctrl + K dd
ペースト Ctrl + U p

※注:nanoは常に「挿入モード」なのでviのようにモード切替がない

ついやってしまうこと

  • ESC を連打する → しかし、なにもおこらなかった!
  • :wq / :q! をタイプしがち → そのまま文字入力される

個人的な所感

一番焦るのは、どうやって終了するか分からない時ですが、とりあえず最々低限として上の3つ(入力保存終了)さえ覚えておけば、軽作業なら何とかなりそうです。