Skip to content

Latest commit

 

History

History
186 lines (130 loc) · 5.66 KB

File metadata and controls

186 lines (130 loc) · 5.66 KB

Decision Point #3: Tailscale Network Validation

Current Status

Feature is optional - Tailscale validation only applies when users create shares with tailscale_only=true 📝 Your choice - Implement if you want to restrict certain shares to your Tailscale network

How It Works Now

Without Tailscale validation (current default):

  • tailscale_only=false shares → Accessible from anywhere ✅
  • tailscale_only=true shares → Still accessible from anywhere ⚠️ (validation disabled)

With Tailscale validation (when implemented):

  • tailscale_only=false shares → Accessible from anywhere ✅
  • tailscale_only=true shares → Only accessible from Tailnet ✅ (validated)

When Do You Need This?

Skip Tailscale validation if:

  • You only use the public share gateway (all shares are tailscale_only=false)
  • You trust users not to abuse tailscale_only flag
  • Simpler setup is more important than this specific security control

Implement Tailscale validation if:

  • You want users to create Tailnet-only shares (private conversations)
  • You expose ushadow directly to your Tailnet (not just via gateway)
  • You need strong network-based access control

Implementation Options

Option A: IP Range Check (Recommended for Direct Tailscale)

If ushadow runs directly as a Tailscale node (not behind a proxy):

# In share_service.py:_validate_tailscale_access(), around line 465:

try:
    ip = ipaddress.ip_address(request_ip)
    tailscale_range = ipaddress.ip_network("100.64.0.0/10")
    is_tailscale = ip in tailscale_range
    logger.debug(f"IP {request_ip} {'is' if is_tailscale else 'is NOT'} in Tailscale range")
    return is_tailscale
except ValueError:
    logger.warning(f"Invalid IP address: {request_ip}")
    return False

How it works:

  • Tailscale uses CGNAT IP range 100.64.0.0/10
  • Check if request IP falls in this range
  • Fast, no API calls

Pros: Simple, fast, no external dependencies Cons: Only works if ushadow is directly on Tailscale (not behind nginx/proxy)

Enable: Set SHARE_VALIDATE_TAILSCALE=true in .env


Option B: Tailscale Serve Headers (For Tailscale Serve Setup)

If you expose ushadow via Tailscale Serve (reverse proxy):

Current limitation: This requires passing the full Request object, not just IP.

Architecture change needed:

# In share_service.py:validate_share_access()
# Instead of:
is_tailscale = await self._validate_tailscale_access(request_ip)

# Pass full request:
is_tailscale = await self._validate_tailscale_access(request)

# In _validate_tailscale_access():
async def _validate_tailscale_access(self, request: Request) -> bool:
    tailscale_user = request.headers.get("X-Tailscale-User")
    if tailscale_user:
        logger.debug(f"Validated Tailscale user: {tailscale_user}")
        return True
    return False

How it works:

  • Tailscale Serve adds X-Tailscale-User header with authenticated user
  • If header present → user is on your Tailnet
  • Cryptographically verified by Tailscale

Pros: Most secure, user identity available Cons: Requires refactoring to pass Request object, only works with Tailscale Serve


Option C: Skip Validation (Current Default)

Don't set SHARE_VALIDATE_TAILSCALE=true and leave as-is.

What happens:

  • All shares work regardless of IP
  • tailscale_only flag is ignored (becomes cosmetic)
  • Simpler setup, no code changes needed

Trade-off: Users can't create truly Tailnet-restricted shares


My Recommendation

For Your Use Case (Public Gateway Architecture):

Skip Tailscale validation for now because:

  1. Your architecture: Friends access via public gateway, not directly to ushadow
  2. Gateway handles it: The gateway itself is on your Tailnet, providing network isolation
  3. Simpler: One less thing to configure and maintain
  4. The flag still useful: Even without validation, tailscale_only serves as metadata/intent

When you WOULD need it:

  • If users access ushadow directly via Tailscale (not just gateway)
  • If you want to enforce Tailnet-only shares for specific conversations

Architecture Reminder

Public Share (tailscale_only=false):
Friend → Public Gateway → [Tailscale] → ushadow

Tailscale-Only Share (tailscale_only=true):
Friend on your Tailnet → ushadow (direct access)
   ↑ THIS is where Tailscale validation matters

The validation prevents a friend from accessing a tailscale_only share via the public gateway or from outside your network.


Testing Your Implementation

Once implemented:

# 1. Create Tailscale-only share
curl -X POST http://localhost:8080/api/share/create \
  -H "Content-Type: application/json" \
  -H "Cookie: ushadow_auth=YOUR_TOKEN" \
  -d '{
    "resource_type": "conversation",
    "resource_id": "abc123",
    "permissions": ["read"],
    "tailscale_only": true
  }'

# 2. Try to access from Tailscale IP (100.64.x.x)
# Expected: ✅ Access granted

# 3. Try to access from public IP (not Tailscale)
# Expected: ❌ 403 "Access restricted to Tailscale network"

Summary

Option When to Use Complexity Security
Skip Public gateway only ⭐ Easy Medium (gateway isolated)
IP Range Direct Tailscale access ⭐⭐ Medium High (network-level)
Serve Headers Tailscale Serve setup ⭐⭐⭐ Complex Highest (crypto verified)

Recommended: Skip for now, implement later if needed.


Next Steps

  1. Decide: Do you need Tailscale-only shares?
  2. If No: Leave as-is, move to frontend integration
  3. If Yes: Set SHARE_VALIDATE_TAILSCALE=true and add 5-10 lines (Option A)