Skip to content

Commit 1874ea9

Browse files
committed
use pydantic settings
1 parent 7772477 commit 1874ea9

4 files changed

Lines changed: 49 additions & 20 deletions

File tree

‎src/attackmate_api_server/auth_utils.py‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import os
32
import secrets
43
from datetime import datetime, timedelta, timezone
54
from typing import Any, Dict, Optional
@@ -8,11 +7,11 @@
87
from fastapi import Depends, HTTPException, status
98
from fastapi.security import APIKeyHeader
109
from passlib.context import CryptContext
10+
from attackmate_api_server.config import settings
1111

1212
load_dotenv()
1313

1414

15-
TOKEN_EXPIRE_MINUTES = int(os.getenv('TOKEN_EXPIRE_MINUTES', 30))
1615
API_KEY_HEADER_NAME = 'X-Auth-Token'
1716
api_key_header_scheme = APIKeyHeader(name=API_KEY_HEADER_NAME, auto_error=True)
1817
pwd_context = CryptContext(schemes=['argon2'], deprecated='auto')
@@ -31,15 +30,15 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
3130

3231

3332
def get_user_hash(username: str) -> Optional[str]:
34-
"""Fetches the hashed password from environment variables."""
35-
env_var_name = f'USER_{username.upper()}_HASH'
36-
return os.getenv(env_var_name)
33+
"""Fetches the hashed password from the centralized settings."""
34+
logger.info(f"user hashses from settings: {settings}")
35+
return settings.users.get(username.lower())
3736

3837

3938
def create_access_token(username: str) -> str:
4039
"""Creates a new token, stores it, and returns the token string."""
4140
token = secrets.token_urlsafe(32)
42-
expires = datetime.now(timezone.utc) + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
41+
expires = datetime.now(timezone.utc) + timedelta(minutes=settings.token_expire_minutes)
4342
# TODO locking needed for multi-threaded access, smth like with token_lock ?
4443
ACTIVE_TOKENS[token] = {'username': username, 'expires': expires}
4544
logger.info(f"Created new token for user '{username}' expiring at {expires}")
@@ -53,7 +52,7 @@ def renew_token_expiry(token: str) -> bool:
5352
"""
5453
token_data = ACTIVE_TOKENS.get(token)
5554
if token_data:
56-
token_data['expires'] = datetime.now(timezone.utc) + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
55+
token_data['expires'] = datetime.now(timezone.utc) + timedelta(minutes=settings.token_expire_minutes)
5756
logger.debug(f"Renewed token expiry for user '{token_data['username']}'")
5857
return True
5958
return False
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
from typing import Dict, Optional
3+
from pydantic_settings import BaseSettings, SettingsConfigDict
4+
from pydantic import Field
5+
6+
7+
class Settings(BaseSettings):
8+
# App Settings
9+
# Environment Var: ATTACKMATE_CONFIG_PATH
10+
attackmate_config_path: Optional[str] = Field(default=None, alias="ATTACKMATE_CONFIG_PATH")
11+
token_expire_minutes: int = 30
12+
13+
# SSL Settings
14+
ssl_key_file: str = "key.pem"
15+
ssl_cert_file: str = "cert.pem"
16+
17+
# User Hashes: Key is username (lowercase), Value is argon2 hash
18+
# Environment Var: users='{"user1": "hash1"}'
19+
users: Dict[str, str] = {}
20+
21+
model_config = SettingsConfigDict(
22+
env_file=".env",
23+
env_file_encoding="utf-8",
24+
# Allows extra env vars without crashing
25+
extra="ignore"
26+
)
27+
28+
29+
settings = Settings()
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
21
from passlib.context import CryptContext
2+
import json
33

44
pwd_context = CryptContext(schemes=['argon2'], deprecated='auto')
55

6-
6+
# Define your users here: { 'username': 'plain_text_password' }
77
users = {
88
'testuser': 'testuser',
9+
'admin': 'change_me_immediately',
10+
'analyst': 'secure_password_123'
911
}
1012

11-
env_content = ''
12-
print('\nCopy the following lines into your .env file:\n')
13-
for username, plain_password in users.items():
14-
hashed_password = pwd_context.hash(plain_password)
15-
env_line = f"USER_{username.upper()}_HASH=\"{hashed_password}\""
16-
print(env_line)
17-
env_content += env_line + '\n'
13+
hashed_users = {username: pwd_context.hash(password) for username, password in users.items()}
14+
15+
print("\n--- Copy the line below into your .env file ---")
16+
print("# AttackMate API User Credentials")
17+
print(f"USERS='{json.dumps(hashed_users)}'")
18+
print("\n--- End of Environment Variables ---")

‎src/attackmate_api_server/main.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
import attackmate_api_server.state as state
1515
from attackmate_api_server.routers import commands, instances, playbooks
16-
16+
from attackmate_api_server.config import settings
1717
from attackmate_api_server.auth_utils import create_access_token, get_user_hash, verify_password
1818
from attackmate_api_server.schemas import TokenResponse
1919

2020
CERT_DIR = os.path.dirname(os.path.abspath(__file__))
21-
KEY_FILE = os.path.join(CERT_DIR, 'key.pem')
22-
CERT_FILE = os.path.join(CERT_DIR, 'cert.pem')
21+
KEY_FILE = os.path.join(CERT_DIR, settings.ssl_key_file)
22+
CERT_FILE = os.path.join(CERT_DIR, settings.ssl_cert_file)
2323

2424
# Initialize loggers
2525
logger = initialize_api_logger(debug=True, append_logs=False)
@@ -31,7 +31,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
3131
logger.info('AttackMate API starting up (lifespan)...')
3232
try:
3333
# Load global config on startup and assign to the variable in state.py
34-
config_path = os.getenv('ATTACKMATE_CONFIG_PATH')
34+
config_path = settings.attackmate_config_path
3535
if config_path:
3636
loaded_config = parse_config(config_file=config_path, logger=logger)
3737
logger.info(f'Loading AttackMate configuration from specified path: {config_path}')

0 commit comments

Comments
 (0)