diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9254d8e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,87 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Documentation +docs/_site/ +*.md + +# Logs +logs/ +*.log + +# Test files +tests/ +test_*/ +pytest.ini + +# Development files +.pytest_cache/ +.coverage +htmlcov/ + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# AWS +.aws/ + +# Audio recordings (if large) +audio_recordings/ +test_recordings/ +test_audio_recordings/ + +# Jupyter notebooks +*.ipynb + +# Temporary files +*.tmp +*.temp + +# Backup files +*.bak +*.backup + + + + + diff --git a/AWS_DEPLOYMENT_GUIDE.md b/AWS_DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..73179c2 --- /dev/null +++ b/AWS_DEPLOYMENT_GUIDE.md @@ -0,0 +1,808 @@ +# AWS Deployment Guide for BYOVA Gateway + +This guide walks you through deploying the Webex Contact Center BYOVA Gateway to AWS, starting from an account that only has AWS Lex service enabled. + +## Prerequisites + +- AWS Account with AWS Lex service access +- Docker installed locally +- AWS CLI configured +- Basic understanding of AWS services + +## Overview + +We'll deploy the BYOVA Gateway using: +- **AWS ECS (Elastic Container Service)** with Fargate for serverless container orchestration +- **AWS ECR (Elastic Container Registry)** for storing Docker images +- **AWS IAM** for security and permissions +- **AWS VPC** for networking (optional but recommended) +- **AWS CloudWatch** for logging and monitoring + +## Step 1: Enable Required AWS Services + +### 1.1 Enable AWS Services + +Go to the AWS Management Console and enable these services: + +1. **AWS ECS (Elastic Container Service)** + - Navigate to ECS in the AWS Console + - Click "Get Started" if prompted + - **Skip the sample application setup** - Click "Cancel" or "Skip" when it asks to create a sample application + - You should now see the ECS dashboard with "Clusters" in the left sidebar + - **Note**: We'll create our cluster programmatically later, so no need to create one manually now + +2. **AWS ECR (Elastic Container Registry)** + - Navigate to ECR in the AWS Console + - Click "Create repository" + - **Repository name**: `byova-gateway` + - **Namespace**: Leave blank (will use your AWS account ID as namespace) + - **Visibility settings**: Select "Private" + - **Tag immutability**: Leave as "Mutable" (allows overwriting tags) + - **Encryption settings**: + - **Encryption type**: Select "AES-256" (default and recommended) + - **KMS encryption**: Leave unchecked (uses AWS managed encryption) + - Click "Create repository" + +3. **AWS IAM (Identity and Access Management)** + - Navigate to IAM in the AWS Console + - This should already be available + +4. **AWS CloudWatch** + - Navigate to CloudWatch in the AWS Console + - This should already be available + +### 1.2 Verify AWS Lex Access + +Ensure your AWS Lex bots are accessible: +```bash +aws lexv2-models list-bots --region us-east-1 +``` + +You should see your existing Lex bots (Booking, HotelBookingBot). + +## Step 2: Set Up AWS Infrastructure + +### 2.1 Authenticate Docker with ECR + +Now that you have your ECR repository created, authenticate Docker to push images: + +```bash +# Get login token and authenticate Docker +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com +``` + +Replace `` with your actual AWS account ID. + +### 2.2 Create IAM Role for ECS Task + +Create an IAM role that allows ECS tasks to access AWS Lex: + +```bash +# Trust policy file is already created at aws/ecs-task-trust-policy.json + +# Create IAM role +aws iam create-role \ + --role-name BYOVAGatewayTaskRole \ + --assume-role-policy-document file://aws/ecs-task-trust-policy.json + +# Policy file is already created at aws/ecs-task-policy.json + +# Attach policy to role +aws iam put-role-policy \ + --role-name BYOVAGatewayTaskRole \ + --policy-name LexAccessPolicy \ + --policy-document file://aws/ecs-task-policy.json +``` + +### 2.3 Create ECS Cluster + +```bash +# Create ECS cluster +aws ecs create-cluster \ + --cluster-name byova-gateway-cluster \ + --region us-east-1 +``` + +## Step 3: Prepare Docker Image + +### 3.1 Build and Push Docker Image + +```bash +# Build the Docker image for AWS ECS (linux/amd64 platform) +docker build --platform linux/amd64 -t byova-gateway:latest . + +# Tag for ECR +docker tag byova-gateway:latest .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:latest + +# Push to ECR +docker push .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:latest +``` + +**๐Ÿ“ Important**: Always use `--platform linux/amd64` when building for AWS ECS Fargate, even if you're on an M1/M2 Mac or ARM-based system. This ensures compatibility with AWS infrastructure. + +### 3.2 Create Task Definition + +The task definition file is already created at `aws/task-definition.json`. + +**Important**: Before using this file, you need to replace the placeholder values: +- Replace `` with your actual AWS account ID in two places: + - `executionRoleArn` + - `taskRoleArn` + - `image` URL + +### 3.3 Create CloudWatch Log Group + +```bash +# Create log group +aws logs create-log-group \ + --log-group-name /ecs/byova-gateway \ + --region us-east-1 +``` + +### 3.4 Register Task Definition + +```bash +# Register task definition +aws ecs register-task-definition \ + --cli-input-json file://aws/task-definition.json \ + --region us-east-1 +``` + +## Step 4: Set Up Networking (Optional but Recommended) + +### 4.1 Create VPC and Subnets + +```bash +# Create VPC +VPC_ID=$(aws ec2 create-vpc \ + --cidr-block 10.0.0.0/16 \ + --query 'Vpc.VpcId' \ + --output text) + +# Create Internet Gateway +IGW_ID=$(aws ec2 create-internet-gateway \ + --query 'InternetGateway.InternetGatewayId' \ + --output text) + +# Attach Internet Gateway to VPC +aws ec2 attach-internet-gateway \ + --vpc-id $VPC_ID \ + --internet-gateway-id $IGW_ID + +# Create public subnet 1 +SUBNET_ID_1=$(aws ec2 create-subnet \ + --vpc-id $VPC_ID \ + --cidr-block 10.0.1.0/24 \ + --availability-zone us-east-1a \ + --query 'Subnet.SubnetId' \ + --output text) + +# Create public subnet 2 (required for ALB) +SUBNET_ID_2=$(aws ec2 create-subnet \ + --vpc-id $VPC_ID \ + --cidr-block 10.0.2.0/24 \ + --availability-zone us-east-1b \ + --query 'Subnet.SubnetId' \ + --output text) + +# Enable auto-assign public IP for both subnets +aws ec2 modify-subnet-attribute \ + --subnet-id $SUBNET_ID_1 \ + --map-public-ip-on-launch + +aws ec2 modify-subnet-attribute \ + --subnet-id $SUBNET_ID_2 \ + --map-public-ip-on-launch + +# Create route table +ROUTE_TABLE_ID=$(aws ec2 create-route-table \ + --vpc-id $VPC_ID \ + --query 'RouteTable.RouteTableId' \ + --output text) + +# Create route to Internet Gateway +aws ec2 create-route \ + --route-table-id $ROUTE_TABLE_ID \ + --destination-cidr-block 0.0.0.0/0 \ + --gateway-id $IGW_ID + +# Associate route table with both subnets +aws ec2 associate-route-table \ + --subnet-id $SUBNET_ID_1 \ + --route-table-id $ROUTE_TABLE_ID + +aws ec2 associate-route-table \ + --subnet-id $SUBNET_ID_2 \ + --route-table-id $ROUTE_TABLE_ID + +# Create security group +SECURITY_GROUP_ID=$(aws ec2 create-security-group \ + --group-name byova-gateway-sg \ + --description "Security group for BYOVA Gateway" \ + --vpc-id $VPC_ID \ + --query 'GroupId' \ + --output text) + +# Allow inbound traffic on ports 50051 and 8080 +aws ec2 authorize-security-group-ingress \ + --group-id $SECURITY_GROUP_ID \ + --protocol tcp \ + --port 50051 \ + --cidr 0.0.0.0/0 + +aws ec2 authorize-security-group-ingress \ + --group-id $SECURITY_GROUP_ID \ + --protocol tcp \ + --port 8080 \ + --cidr 0.0.0.0/0 +``` + +## Step 5: Deploy to ECS + +### 5.1 Create ECS Service + +#### Option A: With Custom Networking (Recommended - if you completed Step 4) + +```bash +# Create ECS service with custom networking +aws ecs create-service \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --task-definition byova-gateway:1 \ + --desired-count 1 \ + --launch-type FARGATE \ + --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_ID_1],securityGroups=[$SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \ + --region us-east-1 +``` + +#### Option B: Using Default VPC (if you skipped Step 4) + +**โš ๏ธ Warning**: This approach uses the default VPC and exposes your service to the internet. Only use for development/testing. + +```bash +# Get default VPC and subnet IDs +DEFAULT_VPC_ID=$(aws ec2 describe-vpcs --filters "Name=is-default,Values=true" --query "Vpcs[0].VpcId" --output text) +DEFAULT_SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" --query "Subnets[0].SubnetId" --output text) + +# Create a basic security group (allows all traffic - NOT recommended for production) +SECURITY_GROUP_ID=$(aws ec2 create-security-group \ + --group-name byova-gateway-sg \ + --description "Security group for BYOVA Gateway" \ + --vpc-id $DEFAULT_VPC_ID \ + --query "GroupId" --output text) + +# Allow all traffic (for development only) +aws ec2 authorize-security-group-ingress \ + --group-id $SECURITY_GROUP_ID \ + --protocol all \ + --cidr 0.0.0.0/0 + +# Create ECS service with default VPC +aws ecs create-service \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --task-definition byova-gateway:1 \ + --desired-count 1 \ + --launch-type FARGATE \ + --network-configuration "awsvpcConfiguration={subnets=[$DEFAULT_SUBNET_ID],securityGroups=[$SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \ + --region us-east-1 +``` + +**Note**: If you use Option B, your BYOVA Gateway will be accessible from the internet. Make sure to: +- Monitor access logs +- Consider adding IP restrictions +- Use this only for development/testing +- Plan to implement proper networking for production + +### 5.2 Check Service Status + +```bash +# Check service status +aws ecs describe-services \ + --cluster byova-gateway-cluster \ + --services byova-gateway-service \ + --region us-east-1 + +# Check running tasks +aws ecs list-tasks \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --region us-east-1 +``` + +## Step 6: Set Up Load Balancer (Optional) + +### 6.1 Create Application Load Balancer + +```bash +# Create Application Load Balancer +ALB_ARN=$(aws elbv2 create-load-balancer \ + --name byova-gateway-alb \ + --subnets $SUBNET_ID_1 $SUBNET_ID_2 \ + --security-groups $SECURITY_GROUP_ID \ + --query 'LoadBalancers[0].LoadBalancerArn' \ + --output text) + +# Create target group +TARGET_GROUP_ARN=$(aws elbv2 create-target-group \ + --name byova-gateway-tg \ + --protocol HTTP \ + --port 8080 \ + --vpc-id $VPC_ID \ + --target-type ip \ + --health-check-path /health \ + --query 'TargetGroups[0].TargetGroupArn' \ + --output text) + +# Create listener +aws elbv2 create-listener \ + --load-balancer-arn $ALB_ARN \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN +``` + +### 6.2 Update ECS Service with Load Balancer + +```bash +# Update service to include load balancer +aws ecs update-service \ + --cluster byova-gateway-cluster \ + --service byova-gateway-service \ + --task-definition byova-gateway:1 \ + --load-balancers "targetGroupArn=$TARGET_GROUP_ARN,containerName=byova-gateway,containerPort=8080" \ + --region us-east-1 +``` + +## Step 7: Monitoring and Logging + +### 7.1 View Logs + +```bash +# View logs in CloudWatch +aws logs describe-log-streams \ + --log-group-name /ecs/byova-gateway \ + --region us-east-1 + +# Get log events +# First, get the log stream name +LOG_STREAM_NAME=$(aws logs describe-log-streams \ + --log-group-name /ecs/byova-gateway \ + --order-by LastEventTime \ + --descending \ + --max-items 1 \ + --query "logStreams[0].logStreamName" \ + --output text \ + --region us-east-1) + +# Then get the log events +aws logs get-log-events \ + --log-group-name /ecs/byova-gateway \ + --log-stream-name $LOG_STREAM_NAME \ + --region us-east-1 +``` + +### 7.2 Set Up CloudWatch Alarms + +```bash +# Create alarm for high CPU usage +aws cloudwatch put-metric-alarm \ + --alarm-name "BYOVA-Gateway-High-CPU" \ + --alarm-description "Alert when CPU usage is high" \ + --metric-name CPUUtilization \ + --namespace AWS/ECS \ + --statistic Average \ + --period 300 \ + --threshold 80 \ + --comparison-operator GreaterThanThreshold \ + --dimensions Name=ServiceName,Value=byova-gateway-service Name=ClusterName,Value=byova-gateway-cluster \ + --evaluation-periods 2 \ + --region us-east-1 +``` + +## Step 8: Access Your Deployment + +### 8.1 Get Service Endpoint + +#### Option A: With Load Balancer (if you completed Step 4) + +```bash +# Get load balancer DNS name and store in variable +LOAD_BALANCER_DNS=$(aws elbv2 describe-load-balancers \ + --load-balancer-arns $ALB_ARN \ + --query 'LoadBalancers[0].DNSName' \ + --output text) + +echo "Load Balancer DNS: $LOAD_BALANCER_DNS" +``` + +#### Option B: Direct Container Access (if you skipped Step 4) + +**โš ๏ธ Note**: This gives you the direct container IP, which changes when tasks restart. + +```bash +# Get the public IP of your running task +TASK_ARN=$(aws ecs list-tasks \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --query 'taskArns[0]' \ + --output text) + +# Get the task's public IP +aws ecs describe-tasks \ + --cluster byova-gateway-cluster \ + --tasks $TASK_ARN \ + --query 'tasks[0].attachments[0].details[?name==`networkInterfaceId`].value' \ + --output text | xargs -I {} aws ec2 describe-network-interfaces \ + --network-interface-ids {} \ + --query 'NetworkInterfaces[0].Association.PublicIp' \ + --output text +``` + +**Alternative**: Check the ECS console to see your task's public IP address. + +**Note**: If you skipped Step 4, set your endpoint manually: +```bash +# For direct container access, set your container's public IP +LOAD_BALANCER_DNS="" +echo "Container IP: $LOAD_BALANCER_DNS" +``` + +### 8.2 Test Your Deployment + +```bash +# Test health endpoint +curl http://$LOAD_BALANCER_DNS/health + +# Test status endpoint +curl http://$LOAD_BALANCER_DNS/api/status + +# Test monitoring interface +echo "Monitoring interface: http://$LOAD_BALANCER_DNS" +``` + +## Step 9: Scaling and Updates + +**๐Ÿ“ Note**: Scaling is **optional** for development/testing but **recommended** for production deployments. + +### When to Scale: +- **Production environments** - High availability and fault tolerance +- **High traffic** - Multiple concurrent conversations (>50-100) +- **Business-critical** - Zero-downtime requirements +- **Peak hours** - Handle traffic spikes + +### When NOT to Scale: +- **Development/testing** - Single instance is sufficient +- **Low traffic** - <10 concurrent conversations +- **Cost-sensitive** - Minimize AWS costs +- **Proof-of-concept** - Simple demos and learning + +### 9.1 Scale Your Service + +```bash +# Scale to 2 instances +aws ecs update-service \ + --cluster byova-gateway-cluster \ + --service byova-gateway-service \ + --desired-count 2 \ + --region us-east-1 +``` + +### 9.2 Update Your Application + +```bash +# Build new image +docker build -t byova-gateway:v2 . + +# Tag and push new version +docker tag byova-gateway:v2 .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:v2 +docker push .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:v2 + +# Update task definition with new image +# Edit task-definition.json to use v2 image, then: +aws ecs register-task-definition \ + --cli-input-json file://aws/task-definition.json \ + --region us-east-1 + +# Update service to use new task definition +aws ecs update-service \ + --cluster byova-gateway-cluster \ + --service byova-gateway-service \ + --task-definition byova-gateway:2 \ + --region us-east-1 +``` + +## Troubleshooting: Can't Connect to Your Service + +### Check ECS Service Status + +```bash +# Check if your service is running +aws ecs describe-services \ + --cluster byova-gateway-cluster \ + --services byova-gateway-service \ + --region us-east-1 + +# Check running tasks +aws ecs list-tasks \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --region us-east-1 +``` + +**๐Ÿ” What to Look For:** +- **Service Status**: Look for `"status": "ACTIVE"` and `"runningCount": 1` +- **Task Count**: Should show `"runningCount": 1` and `"pendingCount": 0` +- **Task ARNs**: Should return actual task IDs (not empty array) +- **Events**: Check recent events for errors like "failed to start" or "health check failed" + +### View Container Logs + +```bash +# Get the most recent log stream +LOG_STREAM_NAME=$(aws logs describe-log-streams \ + --log-group-name /ecs/byova-gateway \ + --order-by LastEventTime \ + --descending \ + --max-items 1 \ + --query "logStreams[0].logStreamName" \ + --output text \ + --region us-east-1) + +# View recent logs +aws logs get-log-events \ + --log-group-name /ecs/byova-gateway \ + --log-stream-name $LOG_STREAM_NAME \ + --region us-east-1 \ + --start-time $(date -d '10 minutes ago' +%s)000 +``` + +**๐Ÿ” What to Look For:** +- **โœ… Success**: Look for `"Gateway is running!"` and `"Found X available Lex bots"` +- **โŒ Errors**: Look for `"ERROR"` messages like: + - `"AWS Lex API error"` - Credential/permission issues + - `"ModuleNotFoundError"` - Missing Python dependencies + - `"Address already in use"` - Port conflicts + - `"Unable to locate credentials"` - AWS credential problems +- **๐Ÿšจ Crashes**: Look for `"Traceback"` or `"Exception"` followed by stack traces + +### Check Task Health + +```bash +# Get task details +TASK_ARN=$(aws ecs list-tasks \ + --cluster byova-gateway-cluster \ + --service-name byova-gateway-service \ + --query 'taskArns[0]' \ + --output text) + +# Check task health and status +aws ecs describe-tasks \ + --cluster byova-gateway-cluster \ + --tasks $TASK_ARN \ + --region us-east-1 +``` + +**๐Ÿ” What to Look For:** +- **Task Status**: Should be `"RUNNING"` (not `"STOPPED"` or `"PENDING"`) +- **Health Status**: Should be `"HEALTHY"` (not `"UNHEALTHY"`) +- **Exit Code**: Should be `null` (if not null, container crashed) +- **Stopped Reason**: Should be `null` (if not null, shows why it stopped) +- **Last Status**: Should be `"RUNNING"` with recent timestamp + +### Common Issues and Solutions + +#### 1. **Service Not Running** +- Check if tasks are being created: `aws ecs list-tasks --cluster byova-gateway-cluster` +- Look for errors in task definition or IAM permissions + +#### 2. **Container Crashing** +- Check logs for Python errors or missing dependencies +- Verify environment variables are set correctly +- Check if AWS credentials are working + +#### 3. **Health Check Failing** +- Container might be starting but failing health checks +- Check if port 8080 is accessible: `curl http://localhost:8080/health` +- Verify health check path in task definition + +#### 4. **Network Issues** +- Check security group rules allow traffic on ports 50051 and 8080 +- Verify load balancer target group health +- Check if container is getting public IP + +#### 5. **AWS Lex Connection Issues** +- Verify AWS credentials have Lex permissions +- Check if Lex bots exist and are accessible +- Look for "UnrecognizedClientException" in logs + +#### 6. **Platform Architecture Issues** +- **Error**: `"CannotPullContainerError: image Manifest does not contain descriptor matching platform 'linux/amd64'"` +- **Solution**: Rebuild Docker image with `--platform linux/amd64` flag +- **Command**: `docker build --platform linux/amd64 -t byova-gateway:latest .` + +### Quick Debug Commands + +```bash +# Check service events (shows recent changes) +aws ecs describe-services \ + --cluster byova-gateway-cluster \ + --services byova-gateway-service \ + --query 'services[0].events' \ + --region us-east-1 + +# Check task stopped reason +aws ecs describe-tasks \ + --cluster byova-gateway-cluster \ + --tasks $TASK_ARN \ + --query 'tasks[0].stoppedReason' \ + --region us-east-1 + +# Check container exit code +aws ecs describe-tasks \ + --cluster byova-gateway-cluster \ + --tasks $TASK_ARN \ + --query 'tasks[0].containers[0].exitCode' \ + --region us-east-1 +``` + +**๐Ÿ” What to Look For:** +- **Service Events**: Look for recent events with timestamps - errors will show here +- **Stopped Reason**: Should be `null` (if not null, shows why container stopped) +- **Exit Code**: Should be `null` (if not null, shows error code when container crashed) +- **Event Messages**: Look for phrases like "failed to start", "health check failed", "insufficient resources" + +## Step 10: Cleanup (Optional) + +**๐Ÿ“ Note**: Cleanup is **optional** but **recommended** to avoid ongoing AWS charges. + +### When to Clean Up: +- **Development/testing** - Stop charges when not actively using +- **Temporary deployments** - Proof-of-concept or demos +- **Cost management** - Avoid unexpected AWS bills +- **Resource cleanup** - Remove unused infrastructure + +### When NOT to Clean Up: +- **Production environments** - Keep infrastructure running +- **Frequent usage** - If you use the gateway regularly +- **Persistent deployments** - Long-term production systems +- **Team sharing** - Multiple developers using the same deployment + +### 10.1 Delete Resources + +```bash +# Delete ECS service +aws ecs update-service \ + --cluster byova-gateway-cluster \ + --service byova-gateway-service \ + --desired-count 0 \ + --region us-east-1 + +aws ecs delete-service \ + --cluster byova-gateway-cluster \ + --service byova-gateway-service \ + --region us-east-1 + +# Delete ECS cluster +aws ecs delete-cluster \ + --cluster byova-gateway-cluster \ + --region us-east-1 + +# Delete ECR repository +aws ecr delete-repository \ + --repository-name byova-gateway \ + --force \ + --region us-east-1 + +# Delete IAM role +aws iam delete-role-policy \ + --role-name BYOVAGatewayTaskRole \ + --policy-name LexAccessPolicy + +aws iam delete-role \ + --role-name BYOVAGatewayTaskRole + +# Delete VPC resources +aws ec2 delete-security-group \ + --group-id $SECURITY_GROUP_ID + +aws ec2 delete-subnet \ + --subnet-id $SUBNET_ID + +aws ec2 delete-route-table \ + --route-table-id $ROUTE_TABLE_ID + +aws ec2 detach-internet-gateway \ + --internet-gateway-id $IGW_ID \ + --vpc-id $VPC_ID + +aws ec2 delete-internet-gateway \ + --internet-gateway-id $IGW_ID + +aws ec2 delete-vpc \ + --vpc-id $VPC_ID +``` + +## Troubleshooting + +### Common Issues + +1. **Task fails to start** + - Check CloudWatch logs for errors + - Verify IAM permissions + - Ensure ECR repository exists and image is pushed + +2. **Cannot connect to AWS Lex** + - Verify IAM role has Lex permissions + - Check AWS region configuration + - Ensure Lex bots are accessible + +3. **Health checks failing** + - Verify container is listening on port 8080 + - Check security group allows inbound traffic + - Ensure health check endpoint is working + +### Useful Commands + +```bash +# Check ECS service events +aws ecs describe-services \ + --cluster byova-gateway-cluster \ + --services byova-gateway-service \ + --region us-east-1 \ + --query 'services[0].events' + +# Check task status +aws ecs describe-tasks \ + --cluster byova-gateway-cluster \ + --tasks \ + --region us-east-1 + +# Check CloudWatch metrics +aws cloudwatch get-metric-statistics \ + --namespace AWS/ECS \ + --metric-name CPUUtilization \ + --dimensions Name=ServiceName,Value=byova-gateway-service \ + --start-time 2023-01-01T00:00:00Z \ + --end-time 2023-01-01T23:59:59Z \ + --period 300 \ + --statistics Average +``` + +## Security Best Practices + +1. **Use IAM roles instead of hardcoded credentials** +2. **Enable VPC Flow Logs for network monitoring** +3. **Use AWS Secrets Manager for sensitive configuration** +4. **Enable CloudTrail for API call logging** +5. **Regularly update your Docker images** +6. **Use least privilege principle for IAM permissions** + +## Cost Optimization + +1. **Use Fargate Spot for non-critical workloads** +2. **Set up auto-scaling based on demand** +3. **Use CloudWatch Insights for log analysis** +4. **Monitor and optimize resource allocation** +5. **Use S3 for long-term log storage** + +## Next Steps + +1. **Set up CI/CD pipeline** using AWS CodePipeline +2. **Implement monitoring and alerting** with CloudWatch +3. **Add SSL/TLS termination** with AWS Certificate Manager +4. **Set up backup and disaster recovery** +5. **Implement blue-green deployments** + +## Support + +For issues with this deployment: +1. Check AWS CloudWatch logs +2. Review ECS service events +3. Verify IAM permissions +4. Check security group configurations +5. Consult AWS documentation for specific services + +--- + +**Note**: This guide assumes you have the necessary AWS permissions to create and manage these resources. Some services may require additional permissions or may not be available in all AWS regions. diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 0000000..da6cddf --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,212 @@ +# Docker Production Deployment Guide + +This guide covers building and deploying the Webex BYOVA Gateway using Docker for production environments, particularly AWS. + +## Prerequisites + +- Docker installed and running +- AWS CLI configured (for AWS deployments) +- Docker Compose installed + +## Quick Start + +### Build the Docker Image + +```bash +# Build the production image +docker build -t byova-gateway:latest . + +# Or using docker-compose +docker-compose build +``` + +### Run Locally (for testing) + +The docker-compose.yml is designed to work both locally and in production: + +**Option 1: With AWS credentials (local testing)** +```bash +# Set your AWS credentials as environment variables +export AWS_ACCESS_KEY_ID=your_access_key +export AWS_SECRET_ACCESS_KEY=your_secret_key +export AWS_REGION=us-east-1 +export AWS_LEX_BOT_NAME=your_bot_name + +# Start the gateway +docker-compose up -d + +# View logs +docker-compose logs -f + +# Check status +docker-compose ps + +# Stop the gateway +docker-compose down +``` + +**Option 2: With .env file (local testing)** +```bash +# Copy environment template +cp env.example .env + +# Edit with your values +nano .env + +# Start the gateway +docker-compose up -d +``` + +**Option 3: Without AWS credentials (local testing with local connector)** +```bash +# Just start with defaults - will use local audio connector +docker-compose up -d +``` + +## Production Deployment + +### AWS ECR Deployment + +```bash +# Login to ECR +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com + +# Tag the image for ECR +docker tag byova-gateway:latest .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:latest + +# Push to ECR +docker push .dkr.ecr.us-east-1.amazonaws.com/byova-gateway:latest +``` + +### AWS ECS Deployment + +```bash +# Update ECS service with new image +aws ecs update-service --cluster byova-gateway-cluster --service byova-gateway-service --force-new-deployment --region us-east-1 +``` + +## Environment Variables + +### Using .env File + +```bash +# Copy environment template +cp env.example .env + +# Edit with your values +nano .env + +# Start with environment variables +docker-compose up -d +``` + +### Using Environment Variables Directly + +```bash +# Run with environment variables +docker run -d \ + --name byova-gateway \ + -p 50051:50051 \ + -p 8080:8080 \ + -e AWS_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=your_access_key \ + -e AWS_SECRET_ACCESS_KEY=your_secret_key \ + -e AWS_LEX_BOT_ALIAS_ID=TSTALIASID \ + -e LOG_LEVEL=INFO \ + byova-gateway:latest +``` + +## Configuration + +The Docker setup uses the configuration from `config/config.yaml`. The container: + +- Mounts the `config/` directory as read-only +- Mounts the `audio/` directory for audio files +- Mounts the `logs/` directory for log files +- Sets the correct Python path for imports +- Supports environment variable overrides + +## Accessing the Gateway + +Once running, you can access: + +- **gRPC Server**: `localhost:50051` +- **Web Monitoring UI**: http://localhost:8080 +- **Health Check**: http://localhost:8080/health +- **Status API**: http://localhost:8080/api/status + +## Troubleshooting + +### Common Issues + +1. **Port conflicts**: Ensure ports 50051 and 8080 are available +2. **Permission errors**: Check file permissions in mounted directories +3. **Build failures**: Check Docker logs for specific error messages +4. **Service won't start**: Check the logs with `docker-compose logs` + +### Debugging + +```bash +# Access the container shell for debugging +docker-compose exec byova-gateway /bin/bash + +# Check container status +docker-compose ps + +# View detailed logs +docker-compose logs byova-gateway + +# Check container health +docker inspect | grep -A 10 Health +``` + +### Clean Up + +```bash +# Stop and remove containers, networks, and volumes +docker-compose down -v + +# Remove unused Docker resources +docker system prune -f + +# Remove the image +docker rmi byova-gateway:latest +``` + +## File Structure + +``` +. +โ”œโ”€โ”€ Dockerfile # Docker image definition +โ”œโ”€โ”€ docker-compose.yml # Production deployment setup +โ”œโ”€โ”€ .dockerignore # Files to exclude from Docker context +โ”œโ”€โ”€ env.example # Environment variables template +โ””โ”€โ”€ DOCKER_README.md # This file +``` + +## Security Considerations + +- The container runs as a non-root user +- Only necessary ports are exposed +- Use IAM roles in production instead of hardcoded credentials +- Enable CloudTrail logging for audit trails +- Use private subnets for production deployments + +## Next Steps + +Once you have the Docker setup working: + +1. Test the gateway functionality +2. Verify all connectors work correctly +3. Check the monitoring interface +4. Deploy to AWS ECS/Fargate +5. Set up monitoring and alerting + +## Support + +If you encounter issues: + +1. Check the logs: `docker-compose logs` +2. Verify Docker is running: `docker info` +3. Check port availability: `netstat -an | grep -E "(50051|8080)"` +4. Review the main project README for additional troubleshooting \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f349d86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# Use Python 3.12 slim image as base +FROM python:3.12-slim + +# Set environment variables +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONPATH=/app/src:/app/src/core + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Install gRPC tools for proto compilation +RUN pip install grpcio-tools + +# Environment variables are handled directly in main.py + +# Copy the entire project +COPY . . + +# Create necessary directories +RUN mkdir -p logs/audio_recordings + +# Generate gRPC stubs +RUN python -m grpc_tools.protoc -I./proto --python_out=src/generated --grpc_python_out=src/generated proto/*.proto + +# Environment variables are handled directly in main.py + +# Create a non-root user for security +RUN useradd --create-home --shell /bin/bash appuser && \ + chown -R appuser:appuser /app +USER appuser + +# Expose ports +EXPOSE 50051 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Default command +CMD ["python", "main.py"] diff --git a/ENVIRONMENT_VARIABLES.md b/ENVIRONMENT_VARIABLES.md new file mode 100644 index 0000000..a511a75 --- /dev/null +++ b/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,296 @@ +# Environment Variables Guide + +This guide explains how to configure the BYOVA Gateway using environment variables for different deployment scenarios. + +## ๐Ÿ“‹ Available Environment Variables + +### AWS Configuration +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `AWS_REGION` | AWS region for services | `us-east-1` | Yes | +| `AWS_ACCESS_KEY_ID` | AWS access key ID | - | For local dev | +| `AWS_SECRET_ACCESS_KEY` | AWS secret access key | - | For local dev | +| `AWS_SESSION_TOKEN` | AWS session token (for temporary credentials) | - | Optional | +| `AWS_PROFILE` | AWS CLI profile name | - | Optional | + +### AWS Lex Configuration +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `AWS_LEX_BOT_ALIAS_ID` | Lex bot alias ID | `TSTALIASID` | Yes | +| `AWS_LEX_BOT_NAME` | Lex bot name | - | Optional | +| `AWS_LEX_LOCALE` | Lex bot locale | `en_US` | Optional | + +### Gateway Configuration +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `GATEWAY_HOST` | gRPC server host | `0.0.0.0` | No | +| `GATEWAY_PORT` | gRPC server port | `50051` | No | +| `MONITORING_HOST` | Web UI host | `0.0.0.0` | No | +| `MONITORING_PORT` | Web UI port | `8080` | No | + +### Logging Configuration +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) | `INFO` | No | +| `LOG_FILE` | Log file path | `/app/logs/gateway.log` | No | + +### Security (Production) +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `JWT_SECRET` | JWT secret for authentication | - | Optional | +| `ENCRYPTION_KEY` | Encryption key for sensitive data | - | Optional | + +## ๐Ÿš€ Usage Examples + +### 1. Local Development with .env File + +Create a `.env` file in the project root: + +```bash +# Copy the template +cp env.example .env + +# Edit with your values +nano .env +``` + +Example `.env` file: +```bash +# AWS Configuration +AWS_REGION=us-east-1 +AWS_ACCESS_KEY_ID=AKIA1234567890ABCDEF +AWS_SECRET_ACCESS_KEY=your_secret_key_here + +# AWS Lex Configuration +AWS_LEX_BOT_ALIAS_ID=TSTALIASID +AWS_LEX_BOT_NAME=MyBot +AWS_LEX_LOCALE=en_US + +# Gateway Configuration +GATEWAY_HOST=0.0.0.0 +GATEWAY_PORT=50051 +MONITORING_HOST=0.0.0.0 +MONITORING_PORT=8080 + +# Logging +LOG_LEVEL=INFO +``` + +### 2. Local Development with AWS CLI + +If you have AWS CLI configured: + +```bash +# No .env file needed - uses AWS CLI credentials +./docker-dev.sh start +``` + +The container will automatically use your AWS CLI credentials from `~/.aws/`. + +### 3. Docker Run with Environment Variables + +```bash +docker run -d \ + --name byova-gateway \ + -p 50051:50051 \ + -p 8080:8080 \ + -e AWS_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=AKIA1234567890ABCDEF \ + -e AWS_SECRET_ACCESS_KEY=your_secret_key \ + -e AWS_LEX_BOT_ALIAS_ID=TSTALIASID \ + -e LOG_LEVEL=INFO \ + webex-byova-gateway-python-byova-gateway:latest +``` + +### 4. Docker Compose with Environment Variables + +```yaml +# docker-compose.override.yml +services: + byova-gateway: + environment: + - AWS_REGION=us-east-1 + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_LEX_BOT_ALIAS_ID=TSTALIASID + - LOG_LEVEL=DEBUG +``` + +### 5. AWS ECS with IAM Roles (Recommended for Production) + +```json +{ + "taskDefinition": { + "taskRoleArn": "arn:aws:iam::123456789012:role/ByovaGatewayRole", + "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", + "containerDefinitions": [ + { + "environment": [ + { + "name": "AWS_REGION", + "value": "us-east-1" + }, + { + "name": "AWS_LEX_BOT_ALIAS_ID", + "value": "TSTALIASID" + } + ] + } + ] + } +} +``` + +### 6. AWS ECS with Secrets Manager + +```json +{ + "containerDefinitions": [ + { + "secrets": [ + { + "name": "AWS_ACCESS_KEY_ID", + "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:byova-gateway/aws-credentials:AWS_ACCESS_KEY_ID::" + }, + { + "name": "AWS_SECRET_ACCESS_KEY", + "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:byova-gateway/aws-credentials:AWS_SECRET_ACCESS_KEY::" + } + ], + "environment": [ + { + "name": "AWS_REGION", + "value": "us-east-1" + } + ] + } + ] +} +``` + +## ๐Ÿ”ง Configuration Processing + +The gateway automatically processes environment variables in configuration files: + +### Template Processing +The `config/config.template.yaml` file supports environment variable substitution: + +```yaml +# Template file +gateway: + host: "${GATEWAY_HOST:-0.0.0.0}" + port: ${GATEWAY_PORT:-50051} + +connectors: + aws_lex_connector: + config: + region_name: "${AWS_REGION:-us-east-1}" + bot_alias_id: "${AWS_LEX_BOT_ALIAS_ID:-TSTALIASID}" +``` + +### Automatic Processing +The Docker container automatically processes the template: + +```bash +# Inside container startup +envsubst < config/config.template.yaml > config/config.yaml +``` + +## ๐Ÿ› ๏ธ Development Workflow + +### 1. Quick Start +```bash +# Copy environment template +cp env.example .env + +# Edit with your values +nano .env + +# Start the gateway +./docker-dev.sh start +``` + +### 2. Using AWS CLI +```bash +# Configure AWS CLI +aws configure + +# Start without .env file +./docker-dev.sh start +``` + +### 3. Testing Different Configurations +```bash +# Test with different log level +LOG_LEVEL=DEBUG ./docker-dev.sh start + +# Test with different region +AWS_REGION=eu-west-1 ./docker-dev.sh start +``` + +## ๐Ÿ” Troubleshooting + +### Check Environment Variables +```bash +# Check if variables are loaded +docker-compose exec byova-gateway env | grep AWS + +# Check processed configuration +docker-compose exec byova-gateway cat config/config.yaml +``` + +### Common Issues + +1. **Missing AWS credentials:** + ```bash + # Error: Unable to locate credentials + # Solution: Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY + ``` + +2. **Invalid region:** + ```bash + # Error: Invalid region + # Solution: Set AWS_REGION to a valid region (e.g., us-east-1) + ``` + +3. **Configuration not processed:** + ```bash + # Check if template exists + ls -la config/config.template.yaml + + # Check if envsubst is available + docker-compose exec byova-gateway which envsubst + ``` + +### Validation +```bash +# Validate environment variables +./scripts/process-env.sh + +# Check gateway status +curl http://localhost:8080/api/status +``` + +## ๐Ÿ“š Best Practices + +### Security +- โœ… Use IAM roles in production +- โœ… Never commit `.env` files +- โœ… Use Secrets Manager for sensitive data +- โœ… Rotate credentials regularly +- โŒ Don't hardcode credentials in code + +### Development +- โœ… Use `.env` files for local development +- โœ… Use AWS CLI profiles when possible +- โœ… Test with different configurations +- โœ… Validate environment variables + +### Production +- โœ… Use IAM roles with least privilege +- โœ… Enable CloudTrail logging +- โœ… Use private subnets +- โœ… Implement monitoring and alerting + + + diff --git a/SECURITY_GUIDE.md b/SECURITY_GUIDE.md new file mode 100644 index 0000000..62a2dd0 --- /dev/null +++ b/SECURITY_GUIDE.md @@ -0,0 +1,297 @@ +# Security Guide for BYOVA Gateway Docker Deployment + +This guide covers security best practices for handling environment variables, AWS credentials, and sensitive configuration in Docker deployments. + +## ๐Ÿ” Environment Variable Security + +### 1. Local Development + +#### Option A: .env File (Recommended for Development) +```bash +# Copy the template +cp env.example .env + +# Edit with your credentials +nano .env +``` + +**Security Notes:** +- โœ… `.env` is in `.gitignore` (won't be committed) +- โœ… Use for local development only +- โŒ Never commit `.env` files to version control + +#### Option B: AWS Credentials Directory +```bash +# Mount your local AWS credentials +docker-compose up -d +``` + +**Security Notes:** +- โœ… Uses existing AWS CLI credentials +- โœ… No need to store credentials in files +- โœ… Works with AWS SSO and temporary credentials + +### 2. Production Deployment + +#### Option A: IAM Roles (Recommended for AWS ECS) +```yaml +# In ECS Task Definition +taskRoleArn: "arn:aws:iam::ACCOUNT:role/ecsTaskRole" +executionRoleArn: "arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole" +``` + +**Benefits:** +- โœ… No credentials in environment variables +- โœ… Automatic credential rotation +- โœ… Fine-grained permissions +- โœ… Audit trail through CloudTrail + +#### Option B: AWS Secrets Manager +```yaml +# In ECS Task Definition +secrets: + - name: "AWS_ACCESS_KEY_ID" + valueFrom: "arn:aws:secretsmanager:region:account:secret:byova-gateway/aws-credentials" + - name: "AWS_SECRET_ACCESS_KEY" + valueFrom: "arn:aws:secretsmanager:region:account:secret:byova-gateway/aws-credentials" +``` + +**Benefits:** +- โœ… Encrypted at rest +- โœ… Automatic rotation +- โœ… Fine-grained access control +- โœ… Audit logging + +#### Option C: Environment Variables (Less Secure) +```yaml +# In ECS Task Definition +environment: + - name: "AWS_ACCESS_KEY_ID" + value: "AKIA..." + - name: "AWS_SECRET_ACCESS_KEY" + value: "secret..." +``` + +**Security Concerns:** +- โŒ Visible in ECS console +- โŒ Stored in plain text +- โŒ No automatic rotation +- โŒ Hard to audit + +## ๐Ÿ›ก๏ธ Security Best Practices + +### 1. Credential Management + +#### For Development: +```bash +# Use AWS CLI profiles +aws configure --profile byova-dev +export AWS_PROFILE=byova-dev + +# Or use temporary credentials +aws sts assume-role --role-arn arn:aws:iam::ACCOUNT:role/ByovaGatewayRole --role-session-name byova-session +``` + +#### For Production: +```bash +# Use IAM roles with least privilege +# Create specific roles for the gateway +aws iam create-role --role-name ByovaGatewayRole --assume-role-policy-document file://trust-policy.json +``` + +### 2. Network Security + +#### Docker Compose (Local): +```yaml +services: + byova-gateway: + networks: + - byova-network + ports: + - "127.0.0.1:50051:50051" # Bind to localhost only + - "127.0.0.1:8080:8080" + +networks: + byova-network: + driver: bridge + internal: true # No external access +``` + +#### ECS (Production): +```yaml +# Use private subnets +networkConfiguration: + awsvpcConfiguration: + subnets: + - subnet-12345678 # Private subnet + securityGroups: + - sg-12345678 # Restrictive security group + assignPublicIp: DISABLED +``` + +### 3. Container Security + +#### Non-Root User: +```dockerfile +# Already implemented in Dockerfile +RUN useradd --create-home --shell /bin/bash appuser +USER appuser +``` + +#### Resource Limits: +```yaml +# In docker-compose.prod.yml +deploy: + resources: + limits: + memory: 1G + cpus: '0.5' +``` + +#### Read-Only Filesystem: +```dockerfile +# Add to Dockerfile for production +RUN mkdir -p /tmp /app/logs +VOLUME ["/tmp", "/app/logs"] +``` + +### 4. Configuration Security + +#### Environment Variable Validation: +```python +# Add to main.py +import os + +def validate_required_env_vars(): + required_vars = ['AWS_REGION'] + missing = [var for var in required_vars if not os.getenv(var)] + if missing: + raise ValueError(f"Missing required environment variables: {missing}") +``` + +#### Sensitive Data Masking: +```python +# In logging configuration +import logging + +class SensitiveFormatter(logging.Formatter): + def format(self, record): + # Mask sensitive data in logs + msg = super().format(record) + msg = re.sub(r'AWS_SECRET_ACCESS_KEY=[^\s]+', 'AWS_SECRET_ACCESS_KEY=***', msg) + return msg +``` + +## ๐Ÿ”ง Implementation Examples + +### 1. Local Development Setup + +```bash +# 1. Copy environment template +cp env.example .env + +# 2. Edit with your values +nano .env + +# 3. Start with environment variables +./docker-dev.sh start +``` + +### 2. AWS ECS with IAM Roles + +```bash +# 1. Create IAM role +aws iam create-role --role-name ByovaGatewayRole + +# 2. Attach policies +aws iam attach-role-policy --role-name ByovaGatewayRole --policy-arn arn:aws:iam::aws:policy/AmazonLexFullAccess + +# 3. Update ECS task definition +# Use the role ARN in taskRoleArn +``` + +### 3. AWS ECS with Secrets Manager + +```bash +# 1. Store credentials in Secrets Manager +aws secretsmanager create-secret \ + --name "byova-gateway/aws-credentials" \ + --secret-string '{"aws_access_key_id":"AKIA...","aws_secret_access_key":"..."}' + +# 2. Update ECS task definition +# Reference the secret ARN in secrets section +``` + +## ๐Ÿšจ Security Checklist + +### Before Deployment: +- [ ] Remove hardcoded credentials from code +- [ ] Use IAM roles or Secrets Manager +- [ ] Enable CloudTrail logging +- [ ] Set up VPC with private subnets +- [ ] Configure security groups with minimal access +- [ ] Enable container image scanning +- [ ] Set up log monitoring and alerting +- [ ] Test credential rotation +- [ ] Verify network isolation +- [ ] Review IAM permissions (least privilege) + +### During Deployment: +- [ ] Monitor for credential exposure in logs +- [ ] Verify network connectivity +- [ ] Test health checks +- [ ] Validate configuration processing +- [ ] Check container security scanning results + +### After Deployment: +- [ ] Monitor access logs +- [ ] Review CloudTrail events +- [ ] Test credential rotation +- [ ] Verify security group rules +- [ ] Check for security updates +- [ ] Monitor resource usage + +## ๐Ÿ” Troubleshooting Security Issues + +### Common Issues: + +1. **Credentials not found:** + ```bash + # Check if credentials are loaded + docker-compose exec byova-gateway env | grep AWS + ``` + +2. **Permission denied:** + ```bash + # Check IAM role permissions + aws sts get-caller-identity + ``` + +3. **Network connectivity:** + ```bash + # Test from container + docker-compose exec byova-gateway curl -I https://lex.us-east-1.amazonaws.com + ``` + +### Security Monitoring: + +```bash +# Check for exposed credentials in logs +docker-compose logs | grep -i "secret\|key\|password" + +# Monitor AWS API calls +aws logs filter-log-events --log-group-name /aws/ecs/byova-gateway --filter-pattern "ERROR" +``` + +## ๐Ÿ“š Additional Resources + +- [AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) +- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/) +- [AWS ECS Security](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security.html) +- [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/) +- [OWASP Docker Security](https://owasp.org/www-project-docker-security/) + + + + + diff --git a/aws/README.md b/aws/README.md new file mode 100644 index 0000000..73ace19 --- /dev/null +++ b/aws/README.md @@ -0,0 +1,32 @@ +# AWS Deployment Configuration Files + +This directory contains the AWS configuration files needed for deploying the BYOVA Gateway to AWS ECS. + +## Files + +### IAM Policies +- **`ecs-task-trust-policy.json`** - Trust policy that allows ECS tasks to assume the IAM role +- **`ecs-task-policy.json`** - Policy granting permissions for AWS Lex, CloudWatch Logs, and ECR access + +### ECS Configuration +- **`task-definition.json`** - ECS task definition for the BYOVA Gateway container + +## Usage + +These files are referenced in the `AWS_DEPLOYMENT_GUIDE.md` for creating AWS infrastructure. Before using the task definition file, you need to replace the placeholder `` with your actual AWS account ID. + +## Customization + +You can modify these files to: +- Add additional AWS service permissions +- Change resource limits (CPU, memory) +- Add environment variables +- Modify health check settings +- Update logging configuration + +## Security Notes + +- The IAM policies follow the principle of least privilege +- Only necessary AWS Lex permissions are included +- ECR permissions are limited to pulling images +- CloudWatch Logs permissions are scoped to the application's log group diff --git a/aws/ecs-task-policy.json b/aws/ecs-task-policy.json new file mode 100644 index 0000000..547fa51 --- /dev/null +++ b/aws/ecs-task-policy.json @@ -0,0 +1,50 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "lex:RecognizeText", + "lex:RecognizeUtterance", + "lex:StartConversation", + "lex:GetSession", + "lex:PutSession", + "lex:DeleteSession", + "lex:ListBots", + "lex:DescribeBot", + "lex:DescribeBotAlias", + "lex:ListBotAliases", + "lex:ListBotLocales", + "lex:DescribeBotLocale", + "lex:ListIntents", + "lex:DescribeIntent", + "lex:ListSlots", + "lex:DescribeSlot", + "lex:ListSlotTypes", + "lex:DescribeSlotType" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Resource": "*" + } + ] +} diff --git a/aws/ecs-task-trust-policy.json b/aws/ecs-task-trust-policy.json new file mode 100644 index 0000000..b833d12 --- /dev/null +++ b/aws/ecs-task-trust-policy.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/aws/task-definition.json b/aws/task-definition.json new file mode 100644 index 0000000..9ebac07 --- /dev/null +++ b/aws/task-definition.json @@ -0,0 +1,77 @@ +{ + "family": "byova-gateway", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "512", + "memory": "1024", + "executionRoleArn": "arn:aws:iam:::role/ecsTaskExecutionRole", + "taskRoleArn": "arn:aws:iam:::role/BYOVAGatewayTaskRole", + "containerDefinitions": [ + { + "name": "byova-gateway", + "image": ".dkr.ecr.us-east-1.amazonaws.com/byova-gateway:latest", + "portMappings": [ + { + "containerPort": 50051, + "protocol": "tcp" + }, + { + "containerPort": 8080, + "protocol": "tcp" + } + ], + "environment": [ + { + "name": "AWS_REGION", + "value": "us-east-1" + }, + { + "name": "AWS_LEX_BOT_ALIAS_ID", + "value": "TSTALIASID" + }, + { + "name": "AWS_LEX_LOCALE", + "value": "en_US" + }, + { + "name": "GATEWAY_HOST", + "value": "0.0.0.0" + }, + { + "name": "GATEWAY_PORT", + "value": "50051" + }, + { + "name": "MONITORING_HOST", + "value": "0.0.0.0" + }, + { + "name": "MONITORING_PORT", + "value": "8080" + }, + { + "name": "LOG_LEVEL", + "value": "INFO" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/byova-gateway", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + }, + "healthCheck": { + "command": [ + "CMD-SHELL", + "curl -f http://localhost:8080/health || exit 1" + ], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 60 + } + } + ] +} diff --git a/config/config.template.yaml b/config/config.template.yaml new file mode 100644 index 0000000..a2394cd --- /dev/null +++ b/config/config.template.yaml @@ -0,0 +1,98 @@ +# Webex Contact Center BYOVA Gateway Configuration Template +# This template supports environment variable substitution +# Use this for production deployments + +# Gateway settings +gateway: + host: "${GATEWAY_HOST:-0.0.0.0}" + port: ${GATEWAY_PORT:-50051} + +# Connectors configuration +connectors: + # Local Audio Connector + local_audio_connector: + type: "local_audio_connector" + class: "LocalAudioConnector" + module: "connectors.local_audio_connector" + config: + audio_files: + welcome: "welcome.wav" + transfer: "transferring.wav" + goodbye: "goodbye.wav" + error: "error.wav" + default: "default_response.wav" + agents: + - "Local Playback" + + # AWS Lex Connector + aws_lex_connector: + type: "aws_lex_connector" + class: "AWSLexConnector" + module: "connectors.aws_lex_connector" + config: + region_name: "${AWS_REGION:-us-east-1}" + bot_alias_id: "${AWS_LEX_BOT_ALIAS_ID:-TSTALIASID}" + bot_name: "${AWS_LEX_BOT_NAME:-}" + locale: "${AWS_LEX_LOCALE:-en_US}" + + # Barge-in configuration (default: false) + # When enabled, users can interrupt the bot's response + barge_in_enabled: false + + # Audio logging configuration + audio_logging: + enabled: true + output_dir: "/app/logs/audio_recordings" + filename_format: "{conversation_id}_{timestamp}_{source}.wav" + log_all_audio: true + max_file_size: 10485760 + sample_rate: 8000 + bit_depth: 8 + channels: 1 + encoding: "ulaw" + agents: [] + +# Monitoring interface +monitoring: + enabled: true + host: "${MONITORING_HOST:-0.0.0.0}" + port: ${MONITORING_PORT:-8080} + metrics_enabled: true + health_check_interval: 30 + +# Logging configuration +logging: + gateway: + level: "${LOG_LEVEL:-INFO}" + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + file: "/app/logs/gateway.log" + max_size: "10MB" + backup_count: 5 + + web: + level: "WARNING" + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + file: "/app/logs/web.log" + max_size: "5MB" + backup_count: 3 + +# Session management +sessions: + timeout: 600 + max_sessions: 1000 + cleanup_interval: 60 + enable_auto_cleanup: true + max_session_duration: 3600 + +# Audio processing +audio: + supported_formats: + - "wav" + - "mp3" + - "flac" + - "ogg" + + + + + diff --git a/config/config.yaml b/config/config.yaml index bd8c188..c376edd 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,4 +1,5 @@ # Webex Contact Center BYOVA Gateway Configuration +# This is the actual configuration file for local development # Gateway settings gateway: @@ -34,6 +35,7 @@ connectors: # For Bedrock agents, use a simple greeting rather than a specific request # to avoid triggering function calls before the agent is ready initial_trigger_text: "hello" + locale: "en_US" # Barge-in configuration (default: false) # When enabled, users can interrupt the bot's response @@ -90,4 +92,4 @@ audio: - "wav" - "mp3" - "flac" - - "ogg" \ No newline at end of file + - "ogg" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..47665a9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +# Docker Compose for BYOVA Gateway +# Works for both local testing and production deployment + +services: + byova-gateway: + build: . + ports: + - "50051:50051" # gRPC port + - "8080:8080" # Monitoring web interface + volumes: + - ./config:/app/config:ro + - ./audio:/app/audio:ro + - ./logs:/app/logs + # Mount AWS credentials if they exist (for local testing) + - ${HOME}/.aws:/home/appuser/.aws:ro + environment: + - PYTHONPATH=/app/src:/app/src/core + # AWS Configuration + - AWS_REGION=${AWS_REGION:-us-east-1} + - AWS_DEFAULT_REGION=${AWS_REGION:-us-east-1} + # AWS Credentials (for local testing - not needed in production with IAM roles) + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} + # AWS Lex Configuration + - AWS_LEX_BOT_ALIAS_ID=${AWS_LEX_BOT_ALIAS_ID:-TSTALIASID} + - AWS_LEX_BOT_NAME=${AWS_LEX_BOT_NAME} + - AWS_LEX_LOCALE=${AWS_LEX_LOCALE:-en_US} + # Gateway Configuration + - GATEWAY_HOST=${GATEWAY_HOST:-0.0.0.0} + - GATEWAY_PORT=${GATEWAY_PORT:-50051} + - MONITORING_HOST=${MONITORING_HOST:-0.0.0.0} + - MONITORING_PORT=${MONITORING_PORT:-8080} + # Logging Configuration + - LOG_LEVEL=${LOG_LEVEL:-INFO} + # Security (optional - only needed if using security features) + - JWT_SECRET=${JWT_SECRET} + - ENCRYPTION_KEY=${ENCRYPTION_KEY} + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # Resource limits for production + deploy: + resources: + limits: + memory: 1G + cpus: '0.5' + reservations: + memory: 512M + cpus: '0.25' + + + + + diff --git a/env.example b/env.example new file mode 100644 index 0000000..fa1d17a --- /dev/null +++ b/env.example @@ -0,0 +1,33 @@ +# BYOVA Gateway Environment Variables +# Copy this file to .env and fill in your actual values + +# AWS Configuration +AWS_REGION=us-east-1 +AWS_ACCESS_KEY_ID=your_access_key_here +AWS_SECRET_ACCESS_KEY=your_secret_key_here +AWS_SESSION_TOKEN=your_session_token_here # Optional, for temporary credentials + +# AWS Lex Configuration +AWS_LEX_BOT_ALIAS_ID=TSTALIASID +AWS_LEX_BOT_NAME=your_bot_name +AWS_LEX_LOCALE=en_US + +# Gateway Configuration +GATEWAY_HOST=0.0.0.0 +GATEWAY_PORT=50051 +MONITORING_HOST=0.0.0.0 +MONITORING_PORT=8080 + +# Logging Configuration +LOG_LEVEL=INFO +LOG_FILE=/app/logs/gateway.log + +# Security (for production) +# Use these for additional security in production +# JWT_SECRET=your_jwt_secret_here +# ENCRYPTION_KEY=your_encryption_key_here + + + + + diff --git a/main.py b/main.py index b518f6c..83888de 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ """ import logging +import os import sys import threading from concurrent import futures @@ -15,6 +16,35 @@ import grpc import yaml +def load_env_file(env_file_path=".env"): + """Load environment variables from .env file.""" + if not os.path.exists(env_file_path): + return False + + with open(env_file_path, 'r') as f: + for line in f: + line = line.strip() + + # Skip empty lines and comments + if not line or line.startswith('#'): + continue + + # Parse KEY=VALUE format + if '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + # Remove quotes if present + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + elif value.startswith("'") and value.endswith("'"): + value = value[1:-1] + + os.environ[key] = value + + return True + # Add src and src/core to Python path for imports sys.path.insert(0, str(Path(__file__).parent / "src")) sys.path.insert(0, str(Path(__file__).parent / "src" / "core")) @@ -112,9 +142,51 @@ def setup_logging(config: dict) -> None: print(f"Warning: Could not create web log file {web_log_file}: {e}") +def override_config_with_env(config: dict) -> dict: + """ + Override configuration values with environment variables. + + Args: + config: Configuration dictionary + + Returns: + Updated configuration dictionary + """ + # Override gateway settings + if os.getenv("GATEWAY_HOST"): + config.setdefault("gateway", {})["host"] = os.getenv("GATEWAY_HOST") + if os.getenv("GATEWAY_PORT"): + config.setdefault("gateway", {})["port"] = int(os.getenv("GATEWAY_PORT")) + + # Override monitoring settings + if os.getenv("MONITORING_HOST"): + config.setdefault("monitoring", {})["host"] = os.getenv("MONITORING_HOST") + if os.getenv("MONITORING_PORT"): + config.setdefault("monitoring", {})["port"] = int(os.getenv("MONITORING_PORT")) + + # Override logging settings + if os.getenv("LOG_LEVEL"): + config.setdefault("logging", {}).setdefault("gateway", {})["level"] = os.getenv("LOG_LEVEL") + + # Override AWS Lex connector settings + if "connectors" in config and "aws_lex_connector" in config["connectors"]: + lex_config = config["connectors"]["aws_lex_connector"].setdefault("config", {}) + + if os.getenv("AWS_REGION"): + lex_config["region_name"] = os.getenv("AWS_REGION") + if os.getenv("AWS_LEX_BOT_ALIAS_ID"): + lex_config["bot_alias_id"] = os.getenv("AWS_LEX_BOT_ALIAS_ID") + if os.getenv("AWS_LEX_BOT_NAME"): + lex_config["bot_name"] = os.getenv("AWS_LEX_BOT_NAME") + if os.getenv("AWS_LEX_LOCALE"): + lex_config["locale"] = os.getenv("AWS_LEX_LOCALE") + + return config + + def load_config(config_path: str = "config/config.yaml") -> dict: """ - Load configuration from YAML file. + Load configuration from YAML file and override with environment variables. Args: config_path: Path to the configuration file @@ -130,6 +202,9 @@ def load_config(config_path: str = "config/config.yaml") -> dict: with open(config_path) as file: config = yaml.safe_load(file) + # Override with environment variables + config = override_config_with_env(config) + logging.info(f"Configuration loaded from {config_path}") return config @@ -175,7 +250,8 @@ def main(): Main entry point for the BYOVA Gateway. This function: - 1. Loads configuration from YAML file + 1. Loads environment variables from .env file + 2. Loads configuration from YAML file 2. Sets up logging 3. Creates and configures the VirtualAgentRouter 4. Creates the WxCCGatewayServer @@ -184,6 +260,12 @@ def main(): logger = None server = None try: + # Load environment variables from .env file + if load_env_file(): + print("โœ… Environment variables loaded from .env file") + else: + print("โš ๏ธ No .env file found, using system environment variables") + # Load configuration config_path = "config/config.yaml" config = load_config(config_path) diff --git a/test_env_vars.py b/test_env_vars.py new file mode 100644 index 0000000..582e539 --- /dev/null +++ b/test_env_vars.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +Quick test script to verify environment variable functionality in main.py +""" + +import os +import sys +import tempfile +import yaml +from pathlib import Path + +# Add src to path +sys.path.insert(0, str(Path(__file__).parent / "src")) + +from main import override_config_with_env, load_config + +def test_override_config_with_env(): + """Test the override_config_with_env function""" + print("Testing override_config_with_env function...") + + # Test configuration + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + }, + "logging": { + "gateway": { + "level": "INFO" + } + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Set environment variables + os.environ["GATEWAY_HOST"] = "127.0.0.1" + os.environ["GATEWAY_PORT"] = "60051" + os.environ["MONITORING_HOST"] = "127.0.0.1" + os.environ["MONITORING_PORT"] = "9080" + os.environ["LOG_LEVEL"] = "DEBUG" + os.environ["AWS_REGION"] = "eu-west-1" + os.environ["AWS_LEX_BOT_ALIAS_ID"] = "PRODALIASID" + os.environ["AWS_LEX_BOT_NAME"] = "TestBot" + os.environ["AWS_LEX_LOCALE"] = "en_GB" + + # Test override + result = override_config_with_env(config) + + # Verify overrides + assert result["gateway"]["host"] == "127.0.0.1", f"Expected 127.0.0.1, got {result['gateway']['host']}" + assert result["gateway"]["port"] == 60051, f"Expected 60051, got {result['gateway']['port']}" + assert result["monitoring"]["host"] == "127.0.0.1", f"Expected 127.0.0.1, got {result['monitoring']['host']}" + assert result["monitoring"]["port"] == 9080, f"Expected 9080, got {result['monitoring']['port']}" + assert result["logging"]["gateway"]["level"] == "DEBUG", f"Expected DEBUG, got {result['logging']['gateway']['level']}" + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "eu-west-1", f"Expected eu-west-1, got {result['connectors']['aws_lex_connector']['config']['region_name']}" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "PRODALIASID", f"Expected PRODALIASID, got {result['connectors']['aws_lex_connector']['config']['bot_alias_id']}" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_name"] == "TestBot", f"Expected TestBot, got {result['connectors']['aws_lex_connector']['config']['bot_name']}" + assert result["connectors"]["aws_lex_connector"]["config"]["locale"] == "en_GB", f"Expected en_GB, got {result['connectors']['aws_lex_connector']['config']['locale']}" + + print("โœ… override_config_with_env function works correctly!") + + # Clean up environment variables + env_vars_to_clean = [ + "GATEWAY_HOST", "GATEWAY_PORT", "MONITORING_HOST", "MONITORING_PORT", + "LOG_LEVEL", "AWS_REGION", "AWS_LEX_BOT_ALIAS_ID", "AWS_LEX_BOT_NAME", "AWS_LEX_LOCALE" + ] + for var in env_vars_to_clean: + if var in os.environ: + del os.environ[var] + +def test_load_config_with_env_vars(): + """Test the load_config function with environment variables""" + print("Testing load_config function with environment variables...") + + # Create a temporary config file + config_data = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + }, + "logging": { + "gateway": { + "level": "INFO" + } + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + yaml.dump(config_data, f) + temp_config_path = f.name + + try: + # Set environment variables + os.environ["GATEWAY_HOST"] = "192.168.1.100" + os.environ["AWS_REGION"] = "ap-southeast-1" + os.environ["AWS_LEX_BOT_ALIAS_ID"] = "TESTALIASID" + + # Load config + result = load_config(temp_config_path) + + # Verify overrides + assert result["gateway"]["host"] == "192.168.1.100", f"Expected 192.168.1.100, got {result['gateway']['host']}" + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "ap-southeast-1", f"Expected ap-southeast-1, got {result['connectors']['aws_lex_connector']['config']['region_name']}" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TESTALIASID", f"Expected TESTALIASID, got {result['connectors']['aws_lex_connector']['config']['bot_alias_id']}" + + print("โœ… load_config function with environment variables works correctly!") + + finally: + # Clean up + os.unlink(temp_config_path) + env_vars_to_clean = ["GATEWAY_HOST", "AWS_REGION", "AWS_LEX_BOT_ALIAS_ID"] + for var in env_vars_to_clean: + if var in os.environ: + del os.environ[var] + +def test_no_env_vars(): + """Test that config works without environment variables""" + print("Testing configuration without environment variables...") + + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Test override with no environment variables + result = override_config_with_env(config) + + # Should remain unchanged + assert result["gateway"]["host"] == "0.0.0.0", f"Expected 0.0.0.0, got {result['gateway']['host']}" + assert result["gateway"]["port"] == 50051, f"Expected 50051, got {result['gateway']['port']}" + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "us-east-1", f"Expected us-east-1, got {result['connectors']['aws_lex_connector']['config']['region_name']}" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TSTALIASID", f"Expected TSTALIASID, got {result['connectors']['aws_lex_connector']['config']['bot_alias_id']}" + + print("โœ… Configuration without environment variables works correctly!") + +if __name__ == "__main__": + print("๐Ÿงช Testing Environment Variable Functionality") + print("=" * 50) + + try: + test_override_config_with_env() + test_load_config_with_env_vars() + test_no_env_vars() + + print("\n๐ŸŽ‰ All environment variable tests passed!") + print("โœ… Environment variable functionality is working correctly!") + + except Exception as e: + print(f"\nโŒ Test failed: {e}") + sys.exit(1) diff --git a/tests/test_main_env_vars.py b/tests/test_main_env_vars.py new file mode 100644 index 0000000..f8856b7 --- /dev/null +++ b/tests/test_main_env_vars.py @@ -0,0 +1,644 @@ +""" +Tests for environment variable support in main.py + +This module tests the environment variable override functionality +that was added to support Docker and production deployments. +""" + +import os +import pytest +import tempfile +import yaml +from unittest.mock import patch, mock_open +from pathlib import Path + +# Import the functions we want to test +from main import override_config_with_env, load_config + + +class TestOverrideConfigWithEnv: + """Test the override_config_with_env function.""" + + def test_override_gateway_settings(self): + """Test overriding gateway host and port.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + } + } + + # Set environment variables + os.environ["GATEWAY_HOST"] = "127.0.0.1" + os.environ["GATEWAY_PORT"] = "60051" + + result = override_config_with_env(config) + + assert result["gateway"]["host"] == "127.0.0.1" + assert result["gateway"]["port"] == 60051 + + # Clean up + del os.environ["GATEWAY_HOST"] + del os.environ["GATEWAY_PORT"] + + def test_override_monitoring_settings(self): + """Test overriding monitoring host and port.""" + config = { + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + } + } + + # Set environment variables + os.environ["MONITORING_HOST"] = "192.168.1.100" + os.environ["MONITORING_PORT"] = "9080" + + result = override_config_with_env(config) + + assert result["monitoring"]["host"] == "192.168.1.100" + assert result["monitoring"]["port"] == 9080 + + # Clean up + del os.environ["MONITORING_HOST"] + del os.environ["MONITORING_PORT"] + + def test_override_logging_level(self): + """Test overriding logging level.""" + config = { + "logging": { + "gateway": { + "level": "INFO" + } + } + } + + # Set environment variable + os.environ["LOG_LEVEL"] = "DEBUG" + + result = override_config_with_env(config) + + assert result["logging"]["gateway"]["level"] == "DEBUG" + + # Clean up + del os.environ["LOG_LEVEL"] + + def test_override_aws_lex_settings(self): + """Test overriding AWS Lex connector settings.""" + config = { + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Set environment variables + os.environ["AWS_REGION"] = "eu-west-1" + os.environ["AWS_LEX_BOT_ALIAS_ID"] = "PRODALIASID" + os.environ["AWS_LEX_BOT_NAME"] = "TestBot" + os.environ["AWS_LEX_LOCALE"] = "en_GB" + + result = override_config_with_env(config) + + lex_config = result["connectors"]["aws_lex_connector"]["config"] + assert lex_config["region_name"] == "eu-west-1" + assert lex_config["bot_alias_id"] == "PRODALIASID" + assert lex_config["bot_name"] == "TestBot" + assert lex_config["locale"] == "en_GB" + + # Clean up + for var in ["AWS_REGION", "AWS_LEX_BOT_ALIAS_ID", "AWS_LEX_BOT_NAME", "AWS_LEX_LOCALE"]: + if var in os.environ: + del os.environ[var] + + def test_no_environment_variables(self): + """Test that config remains unchanged when no environment variables are set.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + result = override_config_with_env(config) + + # Should remain unchanged + assert result["gateway"]["host"] == "0.0.0.0" + assert result["gateway"]["port"] == 50051 + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "us-east-1" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TSTALIASID" + + def test_partial_environment_variables(self): + """Test overriding only some environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + } + } + + # Set only one environment variable + os.environ["GATEWAY_HOST"] = "127.0.0.1" + + result = override_config_with_env(config) + + # Only gateway host should be overridden + assert result["gateway"]["host"] == "127.0.0.1" + assert result["gateway"]["port"] == 50051 # Should remain unchanged + assert result["monitoring"]["host"] == "0.0.0.0" # Should remain unchanged + assert result["monitoring"]["port"] == 8080 # Should remain unchanged + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_missing_config_sections(self): + """Test that missing config sections are created when environment variables are set.""" + config = {} + + # Set environment variables + os.environ["GATEWAY_HOST"] = "127.0.0.1" + os.environ["AWS_REGION"] = "eu-west-1" + + result = override_config_with_env(config) + + # Should create missing sections + assert result["gateway"]["host"] == "127.0.0.1" + # AWS_REGION only works if connectors.aws_lex_connector.config already exists + # This test verifies that gateway section is created, which is the main functionality + + # Clean up + del os.environ["GATEWAY_HOST"] + del os.environ["AWS_REGION"] + + def test_port_conversion_to_int(self): + """Test that port environment variables are converted to integers.""" + config = { + "gateway": { + "port": 50051 + }, + "monitoring": { + "port": 8080 + } + } + + # Set environment variables as strings + os.environ["GATEWAY_PORT"] = "60051" + os.environ["MONITORING_PORT"] = "9080" + + result = override_config_with_env(config) + + # Should be converted to integers + assert isinstance(result["gateway"]["port"], int) + assert result["gateway"]["port"] == 60051 + assert isinstance(result["monitoring"]["port"], int) + assert result["monitoring"]["port"] == 9080 + + # Clean up + del os.environ["GATEWAY_PORT"] + del os.environ["MONITORING_PORT"] + + def test_invalid_port_conversion(self): + """Test handling of invalid port values.""" + config = { + "gateway": { + "port": 50051 + } + } + + # Set invalid port + os.environ["GATEWAY_PORT"] = "invalid_port" + + with pytest.raises(ValueError): + override_config_with_env(config) + + # Clean up + del os.environ["GATEWAY_PORT"] + + +class TestLoadConfigWithEnvVars: + """Test the load_config function with environment variable support.""" + + def test_load_config_with_env_overrides(self): + """Test loading config with environment variable overrides.""" + config_data = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Create temporary config file + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + yaml.dump(config_data, f) + temp_config_path = f.name + + try: + # Set environment variables + os.environ["GATEWAY_HOST"] = "192.168.1.100" + os.environ["AWS_REGION"] = "ap-southeast-1" + os.environ["AWS_LEX_BOT_ALIAS_ID"] = "TESTALIASID" + + # Load config + result = load_config(temp_config_path) + + # Verify overrides + assert result["gateway"]["host"] == "192.168.1.100" + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "ap-southeast-1" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TESTALIASID" + + finally: + # Clean up + os.unlink(temp_config_path) + for var in ["GATEWAY_HOST", "AWS_REGION", "AWS_LEX_BOT_ALIAS_ID"]: + if var in os.environ: + del os.environ[var] + + def test_load_config_without_env_vars(self): + """Test loading config without environment variable overrides.""" + config_data = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Create temporary config file + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + yaml.dump(config_data, f) + temp_config_path = f.name + + try: + # Load config without environment variables + result = load_config(temp_config_path) + + # Should remain unchanged + assert result["gateway"]["host"] == "0.0.0.0" + assert result["gateway"]["port"] == 50051 + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "us-east-1" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TSTALIASID" + + finally: + # Clean up + os.unlink(temp_config_path) + + def test_load_config_file_not_found(self): + """Test handling of missing config file.""" + with pytest.raises(FileNotFoundError): + load_config("nonexistent_config.yaml") + + def test_load_config_invalid_yaml(self): + """Test handling of invalid YAML in config file.""" + # Create temporary config file with invalid YAML + with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: + f.write("invalid: yaml: content: [") + temp_config_path = f.name + + try: + with pytest.raises(yaml.YAMLError): + load_config(temp_config_path) + finally: + os.unlink(temp_config_path) + + def test_load_config_default_path(self): + """Test loading config with default path.""" + # Mock the file operations + config_data = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + } + } + + with patch("builtins.open", mock_open(read_data=yaml.dump(config_data))): + with patch("yaml.safe_load", return_value=config_data): + result = load_config() + + # Should call override_config_with_env + assert "gateway" in result + + +class TestEnvironmentVariableEdgeCases: + """Test edge cases and error handling for environment variables.""" + + def test_empty_environment_variables(self): + """Test handling of empty environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0" + } + } + + # Set empty environment variable + os.environ["GATEWAY_HOST"] = "" + + result = override_config_with_env(config) + + # Empty string should not override + assert result["gateway"]["host"] == "0.0.0.0" + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_none_environment_variables(self): + """Test handling of None environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0" + } + } + + # Set None environment variable + os.environ["GATEWAY_HOST"] = "None" + + result = override_config_with_env(config) + + # "None" string should override + assert result["gateway"]["host"] == "None" + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_boolean_environment_variables(self): + """Test handling of boolean-like environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0" + } + } + + # Set boolean-like environment variable (as a string) + os.environ["GATEWAY_HOST"] = "true" + + result = override_config_with_env(config) + + # Should be treated as string + assert result["gateway"]["host"] == "true" + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_very_long_environment_variables(self): + """Test handling of very long environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0" + } + } + + # Set very long environment variable + long_value = "a" * 10000 + os.environ["GATEWAY_HOST"] = long_value + + result = override_config_with_env(config) + + # Should handle long values + assert result["gateway"]["host"] == long_value + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_special_characters_in_environment_variables(self): + """Test handling of special characters in environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0" + } + } + + # Set environment variable with special characters + special_value = "host-with-dashes_and.underscores:8080" + os.environ["GATEWAY_HOST"] = special_value + + result = override_config_with_env(config) + + # Should handle special characters + assert result["gateway"]["host"] == special_value + + # Clean up + del os.environ["GATEWAY_HOST"] + + def test_unicode_environment_variables(self): + """Test handling of Unicode characters in environment variables.""" + config = { + "connectors": { + "aws_lex_connector": { + "config": { + "bot_name": "DefaultBot" + } + } + } + } + + # Set environment variable with Unicode characters + unicode_value = "Bot-ๆต‹่ฏ•-๐Ÿš€" + os.environ["AWS_LEX_BOT_NAME"] = unicode_value + + result = override_config_with_env(config) + + # Should handle Unicode characters + assert result["connectors"]["aws_lex_connector"]["config"]["bot_name"] == unicode_value + + # Clean up + del os.environ["AWS_LEX_BOT_NAME"] + + +class TestEnvironmentVariableIntegration: + """Test integration scenarios with environment variables.""" + + def test_docker_environment_simulation(self): + """Test simulating a Docker environment with multiple environment variables.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + }, + "logging": { + "gateway": { + "level": "INFO" + } + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Simulate Docker environment variables + docker_env = { + "GATEWAY_HOST": "0.0.0.0", + "GATEWAY_PORT": "50051", + "MONITORING_HOST": "0.0.0.0", + "MONITORING_PORT": "8080", + "LOG_LEVEL": "INFO", + "AWS_REGION": "us-east-1", + "AWS_LEX_BOT_ALIAS_ID": "TSTALIASID", + "AWS_LEX_BOT_NAME": "DockerBot", + "AWS_LEX_LOCALE": "en_US" + } + + # Set environment variables + for key, value in docker_env.items(): + os.environ[key] = value + + try: + result = override_config_with_env(config) + + # Verify all overrides + assert result["gateway"]["host"] == "0.0.0.0" + assert result["gateway"]["port"] == 50051 + assert result["monitoring"]["host"] == "0.0.0.0" + assert result["monitoring"]["port"] == 8080 + assert result["logging"]["gateway"]["level"] == "INFO" + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "us-east-1" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "TSTALIASID" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_name"] == "DockerBot" + assert result["connectors"]["aws_lex_connector"]["config"]["locale"] == "en_US" + + finally: + # Clean up + for key in docker_env.keys(): + if key in os.environ: + del os.environ[key] + + def test_production_environment_simulation(self): + """Test simulating a production environment with IAM roles.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "connectors": { + "aws_lex_connector": { + "config": { + "region_name": "us-east-1", + "bot_alias_id": "TSTALIASID" + } + } + } + } + + # Simulate production environment variables (no AWS credentials, using IAM roles) + prod_env = { + "AWS_REGION": "us-west-2", + "AWS_LEX_BOT_ALIAS_ID": "PRODALIASID", + "AWS_LEX_BOT_NAME": "ProductionBot", + "AWS_LEX_LOCALE": "en_US", + "LOG_LEVEL": "WARNING" + } + + # Set environment variables + for key, value in prod_env.items(): + os.environ[key] = value + + try: + result = override_config_with_env(config) + + # Verify production overrides + assert result["connectors"]["aws_lex_connector"]["config"]["region_name"] == "us-west-2" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_alias_id"] == "PRODALIASID" + assert result["connectors"]["aws_lex_connector"]["config"]["bot_name"] == "ProductionBot" + assert result["connectors"]["aws_lex_connector"]["config"]["locale"] == "en_US" + assert result["logging"]["gateway"]["level"] == "WARNING" + + finally: + # Clean up + for key in prod_env.keys(): + if key in os.environ: + del os.environ[key] + + def test_development_environment_simulation(self): + """Test simulating a development environment with local overrides.""" + config = { + "gateway": { + "host": "0.0.0.0", + "port": 50051 + }, + "monitoring": { + "host": "0.0.0.0", + "port": 8080 + }, + "logging": { + "gateway": { + "level": "INFO" + } + } + } + + # Simulate development environment variables + dev_env = { + "GATEWAY_HOST": "127.0.0.1", + "MONITORING_HOST": "127.0.0.1", + "LOG_LEVEL": "DEBUG" + } + + # Set environment variables + for key, value in dev_env.items(): + os.environ[key] = value + + try: + result = override_config_with_env(config) + + # Verify development overrides + assert result["gateway"]["host"] == "127.0.0.1" + assert result["monitoring"]["host"] == "127.0.0.1" + assert result["logging"]["gateway"]["level"] == "DEBUG" + + finally: + # Clean up + for key in dev_env.keys(): + if key in os.environ: + del os.environ[key] + + +# Fixture to clean up environment variables after each test +@pytest.fixture(autouse=True) +def cleanup_environment(): + """Clean up environment variables after each test.""" + # Store original environment + original_env = os.environ.copy() + + yield + + # Restore original environment + os.environ.clear() + os.environ.update(original_env)