diff --git a/ecs/jskult-webapp/README.md b/ecs/jskult-webapp/README.md index 97632b8f..c65cbf83 100644 --- a/ecs/jskult-webapp/README.md +++ b/ecs/jskult-webapp/README.md @@ -84,6 +84,8 @@ │   ├── exception_handler.py -- FastAPI内部でエラー発生時のハンドリング │   └── exceptions.py -- カスタム例外クラス ├── main.py -- APサーバーのエントリーポイント。ここでルーターやハンドラーの登録を行う + ├── middleware -- ミドルウェアの設定 + │ └── middleware.py ├── model -- モデル層(MVCのM) │   ├── db -- リポジトリから返されるDBレコードのモデル │   │   ├── base_db_model.py @@ -195,3 +197,39 @@ - コントローラーのrouter変数が、`router.route_class = Authenticate`となっている場合、以下の動きをする - リクエスト到達時にセッションの有無をチェックする - レスポンス時、クッキーにセッションキーを登録する + +## HTMLで読み込んでいるスクリプトのSRIハッシュ値を生成・設定する方法 + +### サブリソース完全性 (Subresource Integrity, SRI) とは + +CDN などから取得したリソースが意図せず改ざんされていないかをブラウザーが検証するセキュリティ機能です。 SRI を利用する際には、取得したリソースのハッシュ値と一致すべきハッシュ値を指定します。 + +詳細: + +実消化&アルトマークのWebアプリケーションでは、複数の外部スクリプトを読み込んで動作しているため、読み込むスクリプトを変更した場合は、 +タグの属性値`integrity`に設定されているスクリプトのハッシュ値を更新する必要がある。 + +### SRI ハッシュ値の生成方法(サーバー内のスクリプトについて) + +- サーバー内に保管されているスクリプトを更新した場合、Linux環境(WSL2でも可)で、以下のコマンドを実行し、ハッシュ値を生成する + +```bash +cat <更新したスクリプトファイル名> | openssl dgst -sha384 -binary | openssl base64 -A +``` + +参考: + + +### SRI ハッシュ値の生成方法(外部サイトから読み込んでいるスクリプトについて) + +- 外部サイトから読み込んでいるスクリプトを更新した場合、下記のMDNオンラインツールでハッシュ値を生成する + - [SRI Hash Generator](https://www.srihash.org/) + +### SRI ハッシュ値の設定方法 + +- 更新したスクリプトを読み込んでいる箇所の`integrity`属性値を、生成したハッシュ値に置き換える +- 以下は設定のサンプル + +```bash + +``` diff --git a/ecs/jskult-webapp/src/controller/bio_api.py b/ecs/jskult-webapp/src/controller/bio_api.py index 2f96408a..4989a50d 100644 --- a/ecs/jskult-webapp/src/controller/bio_api.py +++ b/ecs/jskult-webapp/src/controller/bio_api.py @@ -79,6 +79,7 @@ def search_bio_data( 'data': data, 'count': bio_sales_lot_count }) + # クッキーも書き換え json_response.set_cookie( key='session', @@ -152,6 +153,7 @@ async def download_bio_data( 'status': 'ok', 'download_url': download_file_url }) + json_response.set_cookie( key='session', value=session.session_key, diff --git a/ecs/jskult-webapp/src/controller/login.py b/ecs/jskult-webapp/src/controller/login.py index c8a5663c..5c8d904e 100644 --- a/ecs/jskult-webapp/src/controller/login.py +++ b/ecs/jskult-webapp/src/controller/login.py @@ -113,6 +113,7 @@ def login( status_code=status.HTTP_303_SEE_OTHER, headers={'session_key': session_key} ) + return response @@ -170,4 +171,5 @@ def sso_authorize( status_code=status.HTTP_303_SEE_OTHER, headers={'session_key': session_key} ) + return response diff --git a/ecs/jskult-webapp/src/main.py b/ecs/jskult-webapp/src/main.py index 9a19d97b..f0e1807d 100644 --- a/ecs/jskult-webapp/src/main.py +++ b/ecs/jskult-webapp/src/main.py @@ -10,6 +10,7 @@ from src.controller import (bio, bio_api, healthcheck, login, logout, from src.core import task from src.error.exception_handler import http_exception_handler from src.error.exceptions import UnexpectedException +from src.middleware.middleware import SecurityHeadersMiddleware app = FastAPI(openapi_url=None) @@ -42,5 +43,8 @@ app.add_exception_handler(status.HTTP_403_FORBIDDEN, http_exception_handler) # サーバーエラーが発生した場合のハンドラー。HTTPExceptionではハンドリングできないため、個別に設定 app.add_exception_handler(UnexpectedException, http_exception_handler) +# セキュリティヘッダー設定はミドルウェアで処理する +app.add_middleware(SecurityHeadersMiddleware) + # サーバー起動時のイベント app.add_event_handler('startup', task.create_start_app_handler()) diff --git a/ecs/jskult-webapp/src/middleware/__init__.py b/ecs/jskult-webapp/src/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ecs/jskult-webapp/src/middleware/middleware.py b/ecs/jskult-webapp/src/middleware/middleware.py new file mode 100644 index 00000000..264fd190 --- /dev/null +++ b/ecs/jskult-webapp/src/middleware/middleware.py @@ -0,0 +1,16 @@ +from fastapi import Request, Response, status +from fastapi.responses import JSONResponse +from starlette.middleware.base import BaseHTTPMiddleware + +class SecurityHeadersMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request, call_next): + response = await call_next(request) + # X-Frame-Optionsヘッダー追加 + response.headers['X-Frame-Options'] = 'DENY' + # X-Content-Type-Optionsヘッダー追加 + response.headers['X-Content-Type-Options'] = 'nosniff' + # Strict-Transport-Securityヘッダー追加 + response.headers['Strict-Transport-Security'] = 'max-age=31536000 includeSubDomains' + # Cache-Controlヘッダー追加 + response.headers['Cache-Control'] = 'private' + return response diff --git a/ecs/jskult-webapp/src/router/session_router.py b/ecs/jskult-webapp/src/router/session_router.py index a4a28e25..30690590 100644 --- a/ecs/jskult-webapp/src/router/session_router.py +++ b/ecs/jskult-webapp/src/router/session_router.py @@ -103,6 +103,7 @@ class AfterSetCookieSessionRoute(MeDaCaRoute): """事後処理として、セッションキーをcookieに設定するカスタムルートハンドラー""" async def post_process_route(self, request: Request, response: Response): response = await super().post_process_route(request, response) + session_key = response.headers.get('session_key', None) # セッションキーがない場合はセットせずに返す if session_key is None: diff --git a/ecs/jskult-webapp/src/templates/_header.html b/ecs/jskult-webapp/src/templates/_header.html index a0e90606..db30bfb4 100644 --- a/ecs/jskult-webapp/src/templates/_header.html +++ b/ecs/jskult-webapp/src/templates/_header.html @@ -1,7 +1,8 @@ - + + {{subtitle}} @@ -12,8 +13,8 @@ - - - - - + + + + + \ No newline at end of file