feat: machine binding and version enforcement

migration 017 adds machine_id to agents table
middleware validates X-Machine-ID header on authed routes
agent client sends machine ID with requests
MIN_AGENT_VERSION config defaults 0.1.22
version utils added for comparison

blocks config copying attacks via hardware fingerprint
old agents get 426 upgrade required
breaking: <0.1.22 agents rejected
This commit is contained in:
Fimeg
2025-11-02 09:30:04 -05:00
parent 99480f3fe3
commit ec3ba88459
48 changed files with 3811 additions and 122 deletions

24
discord/.env.example Normal file
View File

@@ -0,0 +1,24 @@
# Discord Configuration Template
# Copy this file to .env and fill in your actual values
# Discord Bot Configuration
DISCORD_BOT_TOKEN=your_bot_token_here
DISCORD_SERVER_ID=your_server_id_here
DISCORD_APPLICATION_ID=your_app_id_here
DISCORD_PUBLIC_KEY=your_public_key_here
# Server Management
SERVER_NAME=RedFlag Security
ADMIN_ROLE_ID=your_admin_role_id_here
# Channel IDs (to be filled after creation)
GENERAL_CHANNEL_ID=
ANNOUNCEMENTS_CHANNEL_ID=
SECURITY_ALERTS_CHANNEL_ID=
DEV_CHAT_CHANNEL_ID=
BUG_REPORTS_CHANNEL_ID=
# Category IDs (to be filled after creation)
COMMUNITY_CATEGORY_ID=
DEVELOPMENT_CATEGORY_ID=
SECURITY_CATEGORY_ID=

31
discord/.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
# Environment files
.env
.env.local
.env.*.local
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.env
venv/
.venv/
env/
venv.bak/
venv/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db

426
discord/discord_manager.py Executable file
View File

@@ -0,0 +1,426 @@
#!/usr/bin/env python3
"""
RedFlag Discord Management Bot
Interactive Discord server management with secure configuration
"""
import discord
import asyncio
import logging
import sys
from typing import Optional, Dict, List
from discord.ext import commands
from discord import app_commands
from env_manager import discord_env
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DiscordManager:
"""Interactive Discord server manager"""
def __init__(self):
self.bot_token = discord_env.get_required('DISCORD_BOT_TOKEN')
self.server_id = int(discord_env.get_required('DISCORD_SERVER_ID'))
self.application_id = discord_env.get_required('DISCORD_APPLICATION_ID')
self.public_key = discord_env.get_required('DISCORD_PUBLIC_KEY')
# Bot setup with required intents
intents = discord.Intents.default()
intents.guilds = True
intents.message_content = True
# Initialize bot
self.bot = commands.Bot(
command_prefix='!',
intents=intents,
help_command=None # We'll create custom help
)
self.setup_events()
self.setup_commands()
def setup_events(self):
"""Setup bot event handlers"""
@self.bot.event
async def on_ready():
logger.info(f'✅ Bot logged in as {self.bot.user}')
logger.info(f'Serving server: {self.bot.user.name} (ID: {self.bot.user.id})')
# Sync commands
await self.bot.tree.sync()
logger.info('✅ Commands synced')
# Get server info
guild = self.bot.get_guild(self.server_id)
if guild:
logger.info(f'✅ Connected to server: {guild.name}')
await self.print_server_status(guild)
else:
logger.error('❌ Could not find server!')
@self.bot.event
async def on_command_error(ctx, error):
"""Handle command errors"""
logger.error(f'Command error in {ctx.command}: {error}')
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f'❌ Missing required argument: `{error.param}`')
elif isinstance(error, commands.BadArgument):
await ctx.send(f'❌ Invalid argument: {error}')
else:
await ctx.send(f'❌ An error occurred: {error}')
def setup_commands(self):
"""Setup slash commands"""
# Server management commands
@self.bot.tree.command(name="status", description="Show current Discord server status")
async def cmd_status(interaction: discord.Interaction):
await self.cmd_status(interaction)
@self.bot.tree.command(name="create-channels", description="Create standard RedFlag channels")
async def cmd_create_channels(interaction: discord.Interaction):
await self.cmd_create_channels(interaction)
@self.bot.tree.command(name="send-message", description="Send a message to a channel")
@app_commands.describe(channel="Channel to send message to", message="Message to send")
async def cmd_send_message(interaction: discord.Interaction, channel: str, message: str):
await self.cmd_send_message(interaction, channel, message)
@self.bot.tree.command(name="list-channels", description="List all channels and categories")
async def cmd_list_channels(interaction: discord.Interaction):
await self.cmd_list_channels(interaction)
@self.bot.tree.command(name="create-category", description="Create a channel category")
@app_commands.describe(name="Category name")
async def cmd_create_category(interaction: discord.Interaction, name: str):
await self.cmd_create_category(interaction, name)
@self.bot.tree.command(name="create-test-channel", description="Create one simple test channel")
async def cmd_create_test_channel(interaction: discord.Interaction):
await self.cmd_create_test_channel(interaction)
@self.bot.tree.command(name="help", description="Show available commands")
async def cmd_help(interaction: discord.Interaction):
await self.cmd_help(interaction)
async def cmd_status(self, interaction: discord.Interaction):
"""Show server status"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
embed = discord.Embed(
title="📊 RedFlag Discord Server Status",
color=discord.Color.blue(),
description=f"Server: **{guild.name}**"
)
embed.add_field(name="👥 Members", value=str(guild.member_count), inline=True)
embed.add_field(name="💬 Channels", value=str(len(guild.channels)), inline=True)
embed.add_field(name="🎭 Roles", value=str(len(guild.roles)), inline=True)
embed.add_field(name="📅 Created", value=guild.created_at.strftime("%Y-%m-%d"), inline=True)
embed.add_field(name="👑 Owner", value=f"<@{guild.owner_id}>", inline=True)
embed.add_field(name="🚀 Boost Level", value=str(guild.premium_tier), inline=True)
await interaction.response.send_message(embed=embed)
async def cmd_create_channels(self, interaction: discord.Interaction):
"""Create standard RedFlag channels"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
results = []
# Create categories first
try:
# Community category
community_cat = await guild.create_category_channel("🌍 Community")
discord_env.update_category_ids("community", str(community_cat.id))
results.append("✅ Community category")
# Development category
dev_cat = await guild.create_category_channel("💻 Development")
discord_env.update_category_ids("development", str(dev_cat.id))
results.append("✅ Development category")
# Security category
security_cat = await guild.create_category_channel("🔒 Security")
discord_env.update_category_ids("security", str(security_cat.id))
results.append("✅ Security category")
except Exception as e:
logger.error(f"Error creating categories: {e}")
results.append(f"❌ Categories: {e}")
# Create channels (with small delays to avoid rate limits)
await asyncio.sleep(1)
# Community channels
try:
general = await guild.create_text_channel(
"general",
category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))),
reason="Community general discussion"
)
discord_env.update_channel_ids("general", str(general.id))
results.append("✅ #general")
announcements = await guild.create_text_channel(
"announcements",
category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))),
reason="Project announcements"
)
discord_env.update_channel_ids("announcements", str(announcements.id))
results.append("✅ #announcements")
except Exception as e:
logger.error(f"Error creating community channels: {e}")
results.append(f"❌ Community channels: {e}")
await asyncio.sleep(1)
# Security channels
try:
security_alerts = await guild.create_text_channel(
"security-alerts",
category=discord.Object(id=int(discord_env.get('SECURITY_CATEGORY_ID', 0))),
reason="Security alerts and notifications"
)
discord_env.update_channel_ids("security-alerts", str(security_alerts.id))
results.append("✅ #security-alerts")
except Exception as e:
logger.error(f"Error creating security channels: {e}")
results.append(f"❌ Security channels: {e}")
await asyncio.sleep(1)
# Development channels
try:
dev_chat = await guild.create_text_channel(
"dev-chat",
category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))),
reason="Development discussions"
)
discord_env.update_channel_ids("dev-chat", str(dev_chat.id))
results.append("✅ #dev-chat")
bug_reports = await guild.create_text_channel(
"bug-reports",
category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))),
reason="Bug reports and issues"
)
discord_env.update_channel_ids("bug-reports", str(bug_reports.id))
results.append("✅ #bug-reports")
except Exception as e:
logger.error(f"Error creating development channels: {e}")
results.append(f"❌ Development channels: {e}")
# Send results
embed = discord.Embed(
title="🔧 Channel Creation Results",
color=discord.Color.green() if "" not in str(results) else discord.Color.red(),
description="\n".join(results)
)
await interaction.followup.send(embed=embed, ephemeral=True)
async def cmd_send_message(self, interaction: discord.Interaction, channel: str, message: str):
"""Send a message to a specific channel"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
# Find channel by name
target_channel = discord.utils.get(guild.text_channels, name=channel.lower())
if not target_channel:
await interaction.response.send_message(f"❌ Channel '{channel}' not found!", ephemeral=True)
return
try:
await target_channel.send(message)
await interaction.response.send_message(
f"✅ Message sent to #{channel}!", ephemeral=True
)
except Exception as e:
logger.error(f"Error sending message: {e}")
await interaction.response.send_message(
f"❌ Failed to send message: {e}", ephemeral=True
)
async def cmd_list_channels(self, interaction: discord.Interaction):
"""List all channels and categories"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
embed = discord.Embed(
title="📋 Server Channels",
color=discord.Color.blue()
)
# List categories
categories = [c for c in guild.categories if c.name]
if categories:
category_text = "\n".join([f"**{c.name}** (ID: {c.id})" for c in categories])
embed.add_field(name="📂 Categories", value=category_text or "None", inline=False)
# List text channels
text_channels = [c for c in guild.text_channels if c.category]
if text_channels:
channel_text = "\n".join([f"#{c.name} (ID: {c.id})" for c in text_channels])
embed.add_field(name="💬 Text Channels", value=channel_text or "None", inline=False)
# List voice channels
voice_channels = [c for c in guild.voice_channels if c.category]
if voice_channels:
voice_text = "\n".join([f"🎤 {c.name} (ID: {c.id})" for c in voice_channels])
embed.add_field(name="🎤 Voice Channels", value=voice_text or "None", inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def cmd_create_category(self, interaction: discord.Interaction, name: str):
"""Create a new category"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
try:
category = await guild.create_category_channel(name)
await interaction.response.send_message(
f"✅ Created category: **{name}** (ID: {category.id})",
ephemeral=True
)
except Exception as e:
logger.error(f"Error creating category: {e}")
await interaction.response.send_message(
f"❌ Failed to create category: {e}",
ephemeral=True
)
async def cmd_create_test_channel(self, interaction: discord.Interaction):
"""Create one simple test channel"""
guild = self.bot.get_guild(self.server_id)
if not guild:
await interaction.response.send_message("❌ Could not find server!", ephemeral=True)
return
try:
# Create a simple text channel
test_channel = await guild.create_text_channel(
"test-channel",
reason="Testing bot channel creation"
)
await interaction.response.send_message(
f"✅ Created test channel: **#{test_channel.name}**",
ephemeral=True
)
except Exception as e:
logger.error(f"Error creating test channel: {e}")
await interaction.response.send_message(
f"❌ Failed to create test channel: {e}",
ephemeral=True
)
async def cmd_help(self, interaction: discord.Interaction):
"""Show help information"""
embed = discord.Embed(
title="🤖 RedFlag Discord Bot Help",
description="Interactive Discord server management commands",
color=discord.Color.blue()
)
commands_info = [
("`/status`", "📊 Show server status"),
("`/create-channels`", "🔧 Create standard channels"),
("`/list-channels`", "📋 List all channels"),
("`/send-message`", "💬 Send message to channel"),
("`/create-category`", "📂 Create new category"),
("`/create-test-channel`", "🧪 Create one test channel"),
("`/help`", "❓ Show this help"),
]
for cmd, desc in commands_info:
embed.add_field(name=cmd, value=desc, inline=False)
embed.add_field(
name="🛡️ Security Features",
value="✅ Secure configuration management\n✅ Token protection\n✅ Rate limiting",
inline=False
)
embed.add_field(
name="🔄 Live Updates",
value="Configuration changes are saved instantly to .env file",
inline=False
)
embed.set_footer(text="RedFlag Security Discord Management v1.0")
await interaction.response.send_message(embed=embed, ephemeral=True)
async def print_server_status(self, guild):
"""Print detailed server status to console"""
print(f"\n{'='*60}")
print(f"🚀 REDFLAG DISCORD SERVER STATUS")
print(f"{'='*60}")
print(f"Server Name: {guild.name}")
print(f"Server ID: {guild.id}")
print(f"Members: {guild.member_count}")
print(f"Channels: {len(guild.channels)}")
print(f"Roles: {len(guild.roles)}")
print(f"Owner: <@{guild.owner_id}>")
print(f"Created: {guild.created_at}")
print(f"Boost Level: {guild.premium_tier}")
print(f"{'='*60}\n")
async def run(self):
"""Start the bot"""
try:
# Connect to Discord
await self.bot.start(self.bot_token)
except discord.errors.LoginFailure:
logger.error("❌ Invalid Discord bot token!")
logger.error("Please check your DISCORD_BOT_TOKEN in .env file")
sys.exit(1)
except Exception as e:
logger.error(f"❌ Failed to start bot: {e}")
sys.exit(1)
def main():
"""Main entry point"""
print("🚀 Starting RedFlag Discord Management Bot...")
# Check configuration
if not discord_env.is_configured():
print("❌ Discord not configured!")
print("Please:")
print("1. Copy .env.example to .env")
print("2. Fill in your Discord bot token and server ID")
print("3. Run this script again")
sys.exit(1)
# Create and run bot
bot = DiscordManager()
asyncio.run(bot.run())
if __name__ == "__main__":
main()

153
discord/env_manager.py Executable file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
Secure Discord Environment Manager
Handles loading Discord configuration from .env without exposing secrets
"""
import os
import logging
from typing import Optional, Dict, Any
from dotenv import load_dotenv
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DiscordEnvManager:
"""Secure environment manager for Discord configuration"""
def __init__(self, env_file: str = ".env"):
self.env_file = env_file
self._config = {}
self._load_config()
def _load_config(self):
"""Load configuration from .env file"""
try:
load_dotenv(self.env_file)
self._config = {
'DISCORD_BOT_TOKEN': os.getenv('DISCORD_BOT_TOKEN'),
'DISCORD_SERVER_ID': os.getenv('DISCORD_SERVER_ID'),
'DISCORD_APPLICATION_ID': os.getenv('DISCORD_APPLICATION_ID'),
'DISCORD_PUBLIC_KEY': os.getenv('DISCORD_PUBLIC_KEY'),
'SERVER_NAME': os.getenv('SERVER_NAME', 'RedFlag Security'),
'ADMIN_ROLE_ID': os.getenv('ADMIN_ROLE_ID'),
'GENERAL_CHANNEL_ID': os.getenv('GENERAL_CHANNEL_ID'),
'ANNOUNCEMENTS_CHANNEL_ID': os.getenv('ANNOUNCEMENTS_CHANNEL_ID'),
'SECURITY_ALERTS_CHANNEL_ID': os.getenv('SECURITY_ALERTS_CHANNEL_ID'),
'DEV_CHAT_CHANNEL_ID': os.getenv('DEV_CHAT_CHANNEL_ID'),
'BUG_REPORTS_CHANNEL_ID': os.getenv('BUG_REPORTS_CHANNEL_ID'),
'COMMUNITY_CATEGORY_ID': os.getenv('COMMUNITY_CATEGORY_ID'),
'DEVELOPMENT_CATEGORY_ID': os.getenv('DEVELOPMENT_CATEGORY_ID'),
'SECURITY_CATEGORY_ID': os.getenv('SECURITY_CATEGORY_ID'),
}
# Validate required fields
required_fields = ['DISCORD_BOT_TOKEN', 'DISCORD_SERVER_ID']
missing = [field for field in required_fields if not self._config.get(field)]
if missing:
logger.error(f"Missing required environment variables: {missing}")
raise ValueError(f"Missing required fields: {missing}")
logger.info("✅ Discord configuration loaded successfully")
except Exception as e:
logger.error(f"Failed to load Discord configuration: {e}")
raise
def get(self, key: str, default: Any = None) -> Optional[str]:
"""Get configuration value"""
return self._config.get(key, default)
def get_required(self, key: str) -> str:
"""Get required configuration value"""
value = self._config.get(key)
if not value:
raise ValueError(f"Required environment variable {key} is not set")
return value
def update_channel_ids(self, channel_name: str, channel_id: str):
"""Update channel ID in config"""
channel_key = f"{channel_name.upper()}_CHANNEL_ID"
self._config[channel_key] = channel_id
# Also update in file
self._update_env_file(channel_key, channel_id)
def update_category_ids(self, category_name: str, category_id: str):
"""Update category ID in config"""
category_key = f"{category_name.upper()}_CATEGORY_ID"
self._config[category_key] = category_id
# Also update in file
self._update_env_file(category_key, category_id)
def _update_env_file(self, key: str, value: str):
"""Update .env file with new value"""
try:
env_path = os.path.join(os.path.dirname(__file__), self.env_file)
# Read current file
if os.path.exists(env_path):
with open(env_path, 'r') as f:
lines = f.readlines()
else:
lines = []
# Update or add the line
updated = False
for i, line in enumerate(lines):
if line.startswith(f"{key}="):
lines[i] = f"{key}={value}\n"
updated = True
break
if not updated:
lines.append(f"{key}={value}\n")
# Write back to file
with open(env_path, 'w') as f:
f.writelines(lines)
logger.info(f"✅ Updated {key} in {self.env_file}")
except Exception as e:
logger.error(f"Failed to update {key} in {self.env_file}: {e}")
def is_configured(self) -> bool:
"""Check if the Discord bot is properly configured"""
return (
self.get('DISCORD_BOT_TOKEN') and
self.get('DISCORD_SERVER_ID') and
self.get('DISCORD_APPLICATION_ID')
)
def mask_sensitive_info(self, text: str) -> str:
"""Mask sensitive information in logs"""
sensitive_words = ['TOKEN', 'KEY']
masked_text = text
for word in sensitive_words:
if f"{word}_ID" not in masked_text: # Don't mask channel IDs
# Find and mask the value
import re
pattern = rf'{word}=\w+'
replacement = f'{word}=***MASKED***'
masked_text = re.sub(pattern, replacement, masked_text)
return masked_text
# Global instance for easy access
discord_env = DiscordEnvManager()
# Convenience functions
def get_discord_config():
"""Get Discord configuration"""
return discord_env
def is_discord_ready():
"""Check if Discord is ready for use"""
return discord_env.is_configured()

4
discord/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
discord.py>=2.4.0
python-dotenv>=1.0.0
aiohttp>=3.8.0
asyncio-mqtt>=0.16.0

124
discord/setup.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
RedFlag Discord Setup Assistant
Helps configure Discord bot for server management
"""
import os
import sys
from dotenv import load_dotenv
def setup_discord():
"""Interactive Discord setup"""
print("🚀 RedFlag Discord Bot Setup Assistant")
print("=" * 50)
# Check if .env exists
env_file = ".env"
if not os.path.exists(env_file):
print(f"📝 Creating {env_file} from template...")
if os.path.exists(".env.example"):
import shutil
shutil.copy(".env.example", env_file)
print(f"✅ Created {env_file} from .env.example")
else:
# Create basic .env file
with open(env_file, 'w') as f:
f.write("# Discord Bot Configuration\n")
f.write("DISCORD_BOT_TOKEN=your_bot_token_here\n")
f.write("DISCORD_SERVER_ID=your_server_id_here\n")
f.write("DISCORD_APPLICATION_ID=your_app_id_here\n")
f.write("DISCORD_PUBLIC_KEY=your_public_key_here\n")
f.write("\n# Server Settings\n")
f.write("SERVER_NAME=RedFlag Security\n")
f.write("ADMIN_ROLE_ID=\n")
print(f"✅ Created basic {env_file}")
# Load environment
load_dotenv(env_file)
print("\n📋 Discord Configuration Checklist:")
print("1. ✅ Discord Developer Portal: https://discord.com/developers/applications")
print("2. ✅ Create Application: Click 'New Application'")
print("3. ✅ Create Bot: Go to 'Bot''Add Bot'")
print("4. ✅ Enable Privileged Intents:")
print(" - ✅ Server Members Intent")
print(" - ✅ Server Management Intent")
print(" - ✅ Message Content Intent")
print("5. ✅ OAuth2 URL Generator:")
print(" - ✅ Scope: bot")
print(" - ✅ Scope: applications.commands")
print(" - ✅ Permissions: Administrator (or specific)")
print("6. ✅ Invite Bot to Server")
print("7. ✅ Copy Values Below:")
print("\n🔑 Required Discord Information:")
print("From your Discord Developer Portal, copy these values:")
print("-" * 50)
# Get user input (with masking)
def get_sensitive_input(prompt, key):
value = input(f"{prompt}: ").strip()
if value:
# Update .env file
update_env_file(key, value)
# Show masked version
masked_value = value[:8] + "..." + value[-4:] if len(value) > 12 else value
print(f"{key}: {masked_value}")
return value
def update_env_file(key, value):
"""Update .env file with value"""
env_path = os.path.join(os.path.dirname(__file__), env_file)
# Read current file
with open(env_path, 'r') as f:
lines = f.readlines()
# Update or add the line
updated = False
for i, line in enumerate(lines):
if line.startswith(f"{key}="):
lines[i] = f"{key}={value}\n"
updated = True
break
if not updated:
lines.append(f"{key}={value}\n")
# Write back to file
with open(env_path, 'w') as f:
f.writelines(lines)
# Get required values
bot_token = get_sensitive_input("Discord Bot Token", "DISCORD_BOT_TOKEN")
server_id = get_sensitive_input("Discord Server ID", "DISCORD_SERVER_ID")
app_id = get_sensitive_input("Discord Application ID", "DISCORD_APPLICATION_ID")
public_key = get_sensitive_input("Discord Public Key", "DISCORD_PUBLIC_KEY")
print("-" * 50)
print("🎉 Configuration Complete!")
print("\n📝 Next Steps:")
print("1. Run the Discord bot:")
print(" cd /home/memory/Desktop/Projects/RedFlag/discord")
print(" python discord_manager.py")
print("\n2. Available Commands (slash commands):")
print(" • /status - Show server status")
print(" • /create-channels - Create standard channels")
print(" • /list-channels - List all channels")
print(" • /send-message - Send message to channel")
print(" • /create-category - Create new category")
print(" • /help - Show all commands")
print("\n🔒 Security Note:")
print("• Your bot token is stored locally in .env")
print("• Never share the .env file")
print("• The bot only has Administrator permissions you grant it")
print("• All actions are logged locally")
def main():
"""Main setup function"""
setup_discord()
if __name__ == "__main__":
main()