Skip to content

Commit 528b00f

Browse files
authored
Merge pull request #15 from mdlmarkham/claude/review-test-project-01QwQTFaTsJggDZ27ksb7YhW
Review and test the project
2 parents 57861f6 + af8c891 commit 528b00f

File tree

8 files changed

+619
-15
lines changed

8 files changed

+619
-15
lines changed

SECURITY_REVIEW_REPORT.md

Lines changed: 529 additions & 0 deletions
Large diffs are not rendered by default.

deploy/.env.template

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
SYSTEMMANAGER_AUTH_MODE=oidc
66

77
# OIDC/OAuth Configuration (for TSIDP)
8-
TSIDP_URL=https://tsidp.tailf9480.ts.net
8+
# IMPORTANT: Replace YOUR-TAILNET with your actual Tailscale tailnet name
9+
# Find this in your Tailscale admin console under OAuth Applications
10+
TSIDP_URL=https://tsidp.YOUR-TAILNET.ts.net
911
TSIDP_CLIENT_ID=your-client-id-here
1012
TSIDP_CLIENT_SECRET=your-client-secret-here
11-
SYSTEMMANAGER_BASE_URL=http://dev1.tailf9480.ts.net:8080
13+
# IMPORTANT: Replace with your actual server hostname
14+
SYSTEMMANAGER_BASE_URL=http://your-server.YOUR-TAILNET.ts.net:8080
1215

1316
# Fallback Token Authentication (if not using OIDC)
1417
# SYSTEMMANAGER_SHARED_SECRET=your-hmac-secret-here

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ dependencies = [
1212
"psutil>=5.9.0",
1313
"docker>=6.0.0",
1414
"pydantic>=2.0.0",
15-
"cryptography>=41.0.0",
15+
"cryptography>=42.0.0", # Updated to match requirements.txt
1616
]
1717

1818
classifiers = [

src/auth/token_auth.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,16 @@ def verify(self, token: str) -> TokenClaims:
109109
raise SystemManagerError("invalid token claims", category=ErrorCategory.UNAUTHORIZED)
110110

111111
# Check expiry if present
112-
if claims.expiry and claims.expiry < datetime.datetime.utcnow():
113-
raise SystemManagerError("token expired", category=ErrorCategory.UNAUTHORIZED)
112+
# Use timezone-aware datetime comparison
113+
now = datetime.datetime.now(datetime.timezone.utc)
114+
# Handle both naive and aware datetimes
115+
if claims.expiry:
116+
expiry = claims.expiry
117+
if expiry.tzinfo is None:
118+
# Assume UTC for naive datetimes
119+
expiry = expiry.replace(tzinfo=datetime.timezone.utc)
120+
if expiry < now:
121+
raise SystemManagerError("token expired", category=ErrorCategory.UNAUTHORIZED)
114122

115123
return claims
116124

src/mcp_server.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
from src.server.dependencies import deps
1717
from src.tools import register_all_tools
1818

19-
# Configure logging
20-
logging.basicConfig(level=logging.DEBUG)
19+
# Configure logging - use environment variable for log level
20+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
21+
logging.basicConfig(
22+
level=getattr(logging, LOG_LEVEL, logging.INFO),
23+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24+
)
2125
logger = logging.getLogger(__name__)
22-
# Enable FastMCP auth debugging
23-
logging.getLogger("fastmcp.server.auth").setLevel(logging.DEBUG)
26+
27+
# Enable FastMCP auth debugging only in development
28+
if os.getenv("SYSTEMMANAGER_ENV") == "development":
29+
logging.getLogger("fastmcp.server.auth").setLevel(logging.DEBUG)
2430

2531
# Create FastMCP instance with auth
2632
mcp = create_mcp_instance()

src/mcp_server_legacy.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,17 @@
2323
from src.tools import stack_tools
2424
from src.services.log_analyzer import LogAnalyzer
2525

26-
logging.basicConfig(level=logging.DEBUG)
26+
# Configure logging - use environment variable for log level
27+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
28+
logging.basicConfig(
29+
level=getattr(logging, LOG_LEVEL, logging.INFO),
30+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31+
)
2732
logger = logging.getLogger(__name__)
28-
# Enable FastMCP auth debugging
29-
logging.getLogger("fastmcp.server.auth").setLevel(logging.DEBUG)
33+
34+
# Enable FastMCP auth debugging only in development
35+
if os.getenv("SYSTEMMANAGER_ENV") == "development":
36+
logging.getLogger("fastmcp.server.auth").setLevel(logging.DEBUG)
3037

3138
# Determine authentication mode
3239
AUTH_MODE = os.getenv("SYSTEMMANAGER_AUTH_MODE", "token").lower()

src/server/config.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ def create_mcp_instance() -> FastMCP:
1515

1616
if auth_mode == "oidc":
1717
# TSIDP OIDC Authentication
18-
tsidp_url = os.getenv("TSIDP_URL", "https://tsidp.tailf9480.ts.net")
18+
# Require explicit TSIDP configuration - no hardcoded defaults
19+
tsidp_url = os.getenv("TSIDP_URL")
20+
if not tsidp_url:
21+
raise ValueError(
22+
"TSIDP_URL must be configured when using OIDC authentication. "
23+
"Set TSIDP_URL to your Tailscale Identity Provider URL. "
24+
"Example: TSIDP_URL=https://tsidp.YOUR-TAILNET.ts.net"
25+
)
26+
1927
base_url = os.getenv("SYSTEMMANAGER_BASE_URL", "http://localhost:8080")
2028
client_id = os.getenv("TSIDP_CLIENT_ID")
2129
client_secret = os.getenv("TSIDP_CLIENT_SECRET")

src/services/compose_manager.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,50 @@
88
import subprocess
99
from typing import Dict, List, Optional
1010
from pathlib import Path
11+
from urllib.parse import urlparse
1112
import git
1213

1314

1415
class ComposeStackManager:
1516
"""Manage Docker Compose stacks from Git repositories."""
16-
17+
1718
def __init__(self, stacks_dir: str = "/opt/stacks"):
1819
self.stacks_dir = Path(stacks_dir)
1920
self.stacks_dir.mkdir(parents=True, exist_ok=True)
21+
22+
def _validate_repo_url(self, url: str) -> bool:
23+
"""Validate repository URL is from allowed sources.
24+
25+
Args:
26+
url: Git repository URL to validate
27+
28+
Returns:
29+
True if URL is allowed, False otherwise
30+
"""
31+
# Get allowed hosts from environment, default to common git providers
32+
allowed_hosts_str = os.getenv(
33+
"SYSTEMMANAGER_ALLOWED_GIT_HOSTS",
34+
"github.com,gitlab.com,bitbucket.org"
35+
)
36+
allowed_hosts = [h.strip() for h in allowed_hosts_str.split(",") if h.strip()]
37+
38+
try:
39+
parsed = urlparse(url)
40+
41+
# Only allow https and git protocols
42+
if parsed.scheme not in ['https', 'git']:
43+
return False
44+
45+
# Extract hostname (handle git@host:repo format)
46+
if '@' in parsed.netloc:
47+
host = parsed.netloc.split('@')[-1].split(':')[0]
48+
else:
49+
host = parsed.netloc.split(':')[0]
50+
51+
# Check if host is in allowed list
52+
return any(host == allowed or host.endswith('.' + allowed) for allowed in allowed_hosts)
53+
except Exception:
54+
return False
2055

2156
async def deploy_stack(
2257
self,
@@ -39,8 +74,16 @@ async def deploy_stack(
3974
Deployment status and details
4075
"""
4176
stack_path = self.stacks_dir / stack_name
42-
77+
4378
try:
79+
# Validate repository URL
80+
if not self._validate_repo_url(repo_url):
81+
return {
82+
"success": False,
83+
"error": f"Repository URL not allowed: {repo_url}. Configure SYSTEMMANAGER_ALLOWED_GIT_HOSTS to allow this host.",
84+
"stack": stack_name
85+
}
86+
4487
# Clone or pull repository
4588
if stack_path.exists():
4689
repo = git.Repo(stack_path)

0 commit comments

Comments
 (0)