SaaSのセキュリティ設定の評価
設定の評価に先立って、取得の完全性と権限不足を確認します。
説明
SSPMはSaaSの設定や権限を取得し、組織の基準から外れた状態を見つけます。APIの取得権限やページングに抜けがあると、未取得を正常と誤判定するおそれがあります。
Graph型のページングを持つ固定API資料を使い、取得した設定をSQLへ保存します。前回との差を調べた後、取得できていない項目を別に抽出します。
設定の評価に先立って、取得の完全性と権限不足を確認します。
1先頭ページと継続先を読む
valueには設定行、nextLinkには次ページの場所があります。
from sp14 import *
from sp02 import show
db=connect()
print(PAGES先頭ページと継続先を読む[0])先頭にはa1とa2だけです。2行取得できたことは全件取得の証拠ではありません。
2先頭ページだけを保存する
idとmfaの真偽値を保存します。
db.executemany先頭ページだけを保存する("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[0]['value']])
show(db,"SELECT * FROM current ORDER BY id")a1は1、a2は0です。まだa3は取り込まれていません。
3継続ページを取り込む
最後のページにはnextLinkがありません。
print(PAGES[1])
db.executemany継続ページを取り込む("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[1]['value']])
show(db,"SELECT COUNT(*) AS fetched FROM current")3行になりました。継続リンクはサーバーが返した形を使い、クライアント側で適当にページ番号を推測しません。
4前回から変わった設定を抽出する
同じIDが両方にあるものだけを比較します。
show(db,"SELECT p.id,p.mfa AS before,c.mfa AS after FROM previous p JOIN current c USING(id) WHERE前回から変わった設定を抽出する p.mfa<>c.mfa")a2が1から0へ変わっています。今回の基準はmfa=1なので、この変更が調査対象です。
5取得できていない項目を抽出する
前回にはあり今回にないIDを探します。
show(db,"SELECT p.id,c.mfa FROM previous p LEFT JOIN取得できていない項目を抽出する current c USING(id) WHERE c.id IS NULL")a4はNULLです。削除されたのか、権限や取得対象が変わったのかを追加確認する必要があります。
6権限不足の応答を空の正常結果にしない
APIの固定エラー応答を開きます。
print(ERROR権限不足の応答を空の正常結果にしない['error']['code'])
print('value' in ERROR)Authorization_RequestDeniedでvalueがありません。正常な0件応答に変換せず、取得失敗として扱います。
7不適合と未取得を別々に表示する
0という値とNULLを別の状態へ分類します。
show(db,"SELECT p.id,CASE不適合と未取得を別々に表示する WHEN c.id IS NULL THEN 'not_fetched' WHEN c.mfa=0 THEN 'fails_baseline' ELSE 'passes_baseline' END AS status FROM previous p LEFT JOIN current c USING(id) ORDER BY p.id")a2は基準不適合、a4は未取得です。未取得を合格件数へ含めると評価を過大に見せてしまいます。
まとめ
継続ページと取得権限を確認します。
前回との差と未取得を分けます。
不明な値を基準適合へ変換しません。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite / 固定の主体・権限・操作記録
from sp14 import *
from sp02 import show
db=connect()
print(PAGES[0]){'value': [{'id': 'a1', 'mfa': True}, {'id': 'a2', 'mfa': False}], '@odata.nextLink': 'https://graph.example.test/settings?page=2'}db.executemany("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[0]['value']])
show(db,"SELECT * FROM current ORDER BY id")id | mfa a1 | 1 a2 | 0
print(PAGES[1])
db.executemany("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[1]['value']])
show(db,"SELECT COUNT(*) AS fetched FROM current"){'value': [{'id': 'a3', 'mfa': True}]}
fetched
3show(db,"SELECT p.id,p.mfa AS before,c.mfa AS after FROM previous p JOIN current c USING(id) WHERE p.mfa<>c.mfa")id | before | after a2 | 1 | 0
show(db,"SELECT p.id,c.mfa FROM previous p LEFT JOIN current c USING(id) WHERE c.id IS NULL")id | mfa a4 | NULL
print(ERROR['error']['code'])
print('value' in ERROR)Authorization_RequestDenied False
show(db,"SELECT p.id,CASE WHEN c.id IS NULL THEN 'not_fetched' WHEN c.mfa=0 THEN 'fails_baseline' ELSE 'passes_baseline' END AS status FROM previous p LEFT JOIN current c USING(id) ORDER BY p.id")id | status a1 | passes_baseline a2 | fails_baseline a3 | passes_baseline a4 | not_fetched
演習完了