Skip to content

Commit

Permalink
Fix DMZ/LZ automation and stats
Browse files Browse the repository at this point in the history
  • Loading branch information
aloftus23 committed Dec 19, 2024
1 parent 240f453 commit b67b43d
Show file tree
Hide file tree
Showing 22 changed files with 3,385 additions and 3,333 deletions.
7 changes: 4 additions & 3 deletions backend/env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ integration:
staging:
REGION: us-gov-east-1
ENDPOINT_TYPE: PRIVATE
API_GATEWAY_NAME: staging-crossfeed
LAMBDA_ROLE: crossfeed-staging-us-gov-east-1-lambdaRole
STACK_NAME: crossfeed-staging
COGNITO_URL: https://cognito-idp.us-gov-west-1.amazonaws.com
BACKEND_DOMAIN: https://api.staging.crossfeed.cyber.dhs.gov
EMAIL_REGION: us-gov-west-1
LZ_PROXY_URL: ${ssm:/crossfeed/staging/LZ_PROXY_URL}
DJANGO_KEY: ${ssm:/crossfeed/staging/DJANGO_KEY}
PYTHONPATH: src/xfd_django
DJANGO_SETTINGS_MODULE: xfd_django.settings
DB_DIALECT: postgres
DB_PORT: 5432
DB_HOST: ${ssm:/crossfeed/staging/DATABASE_HOST}
Expand Down Expand Up @@ -207,6 +207,7 @@ prod:
COGNITO_URL: https://cognito-idp.us-gov-west-1.amazonaws.com
BACKEND_DOMAIN: https://api.crossfeed.cyber.dhs.gov
EMAIL_REGION: us-gov-west-1
LZ_PROXY_URL: ${ssm:/crossfeed/prod/LZ_PROXY_URL}
DJANGO_KEY: ${ssm:/crossfeed/prod/DJANGO_KEY}
DB_DIALECT: postgres
DB_PORT: 5432
Expand Down
6,291 changes: 3,158 additions & 3,133 deletions backend/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"description": "",
"devDependencies": {
"serverless": "^3.30",
"serverless-better-credentials": "^2.0.0",
"serverless-domain-manager": "^7.0",
"serverless-dotenv-plugin": "^6.0.0",
"serverless-plugin-ifelse": "^1.0.7",
"serverless-python-requirements": "^6.1.1"
},
"license": "ISC",
Expand Down
66 changes: 43 additions & 23 deletions backend/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,48 @@ custom:
certificateName: ${file(env.yml):${self:provider.stage}.CERT_DOMAIN, ''}
stage: ${self:provider.stage}
createRoute53Record: false
serverlessIfElse:
- If: '"${file(env.yml):${self:provider.stage}.ENDPOINT_TYPE}" == "REGIONAL"'
Exclude:
- provider.vpcEndpointIds
pythonRequirements:
dockerizePip: true
noDeploy:
- boto3
- botocore

# Resource policies for GovCloud (Private) vs Non-GovCloud (Regional)
privateResourcePolicy:
- Effect: Deny
Principal: '*'
Action: 'execute-api:Invoke'
Resource: execute-api:/${self:provider.stage}/*/*
Condition:
StringNotEquals:
'aws:sourceVpce': ${file(env.yml):${self:provider.stage}.VPC_ENDPOINT, ''}
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource: execute-api:/${self:provider.stage}/*/*

regionalResourcePolicy:
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource: execute-api:/${self:provider.stage}/*/*
Condition:
IpAddress:
aws:SourceIp:
- ${file(env.yml):${self:provider.stage}.DMZ_CIDR, ''}

# Conditional logic for GovCloud vs non-GovCloud (Private endpoints require a VPC Endpoint)

Check failure on line 45 in backend/serverless.yml

View workflow job for this annotation

GitHub Actions / lint

45:89 [line-length] line too long (93 > 88 characters)
serverlessIfElse:
- If: '"${file(env.yml):${self:provider.stage}.ENDPOINT_TYPE}" == "PRIVATE"'
Set:
provider.vpcEndpointIds:
- ${file(env.yml):${self:provider.stage}.VPC_ENDPOINT, ''}
provider.apiGateway.resourcePolicy: ${self:custom.privateResourcePolicy}
ElseSet:
provider.apiGateway.resourcePolicy: ${self:custom.regionalResourcePolicy}
ElseExclude:
- provider.vpcEndpointIds

provider:
name: aws
region: ${file(env.yml):${self:provider.stage}.REGION, ''}
Expand All @@ -31,34 +63,20 @@ provider:
stage: ${opt:stage, 'dev'}
environment: ${file(env.yml):${self:provider.stage}, ''}
vpc: ${file(env.yml):${self:provider.stage}-vpc, ''}
# NEED TO MAKE THIS AN IF/ELSE. Cause it's not needed in DMZ
# vpcEndpointIds:
# - ${file(env.yml):${self:provider.stage}.VPC_ENDPOINT, ''}
logs:
restApi: true
deploymentBucket:
serverSideEncryption: AES256
apiGateway:
binaryMediaTypes:
- image/*
- font/*
resourcePolicy:
# This first block here needs to be an if/else too
# - Effect: Deny
# Principal: '*'
# Action: 'execute-api:Invoke'
# Resource: 'execute-api:/${self:provider.stage}/*/*'
# Condition:
# StringNotEquals:
# 'aws:sourceVpce': ${file(env.yml):${self:provider.stage}.VPC_ENDPOINT, ''}
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource: execute-api:/${self:provider.stage}/*/*
Condition:
IpAddress:
aws:SourceIp:
- ${file(env.yml):${self:provider.stage}.DMZ_CIDR, ''}
logs:
restApi: true
deploymentBucket:
serverSideEncryption: AES256

iam:
role:
statements:
Expand Down Expand Up @@ -179,6 +197,8 @@ functions:
- ${file(./src/api/functions.yml)}

plugins:
- serverless-better-credentials
- serverless-domain-manager
- serverless-dotenv-plugin
- serverless-python-requirements
- serverless-plugin-ifelse
35 changes: 3 additions & 32 deletions backend/src/xfd_django/xfd_api/api_methods/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from ..auth import (
get_org_memberships,
get_user_organization_ids,
is_global_view_admin,
is_global_write_admin,
is_org_admin,
Expand Down Expand Up @@ -995,16 +994,12 @@ def list_organizations_v2(state, regionId, current_user):
):
return []

# TODO: MAKE SURE IF Just a normal org member they can only get their org
# Define filter for organizations based on admin status
# org_filter = {}
# if not is_global_view_admin(current_user):
# org_filter["id__in"] = get_org_memberships(current_user)
# org_filter["parent"] = None

# Prepare the filter criteria
filter_criteria = Q()

if not is_global_view_admin(current_user):
filter_criteria &= Q(id__in=get_org_memberships(current_user))

if state:
filter_criteria &= Q(state__in=state)

Expand Down Expand Up @@ -1038,30 +1033,6 @@ def list_organizations_v2(state, regionId, current_user):
"county": org.county,
"countyFips": org.countyFips,
"type": org.type,
"userRoles": [
{
"id": str(role.id),
"role": role.role,
"approved": role.approved,
"user": {
"id": str(role.user.id),
"email": role.user.email,
"firstName": role.user.firstName,
"lastName": role.user.lastName,
"fullName": role.user.fullName,
},
}
for role in org.userRoles.all()
],
"tags": [
{
"id": str(tag.id),
"createdAt": tag.createdAt.isoformat(),
"updatedAt": tag.updatedAt.isoformat(),
"name": tag.name,
}
for tag in org.tags.all()
],
}
for org in organizations
]
Expand Down
50 changes: 27 additions & 23 deletions backend/src/xfd_django/xfd_api/api_methods/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ async def get_severity_stats(


async def stats_latest_vulns(
filter_data, current_user, redis_client, max_results=100, filtered_org_ids=None
filter_data, current_user, redis_client, max_results=50, filtered_org_ids=None
):
"""
Retrieve the latest vulnerabilities from Elasticache filtered by user.
Expand All @@ -255,14 +255,18 @@ async def stats_latest_vulns(
detail="No organizations found for the user with the specified filters.",
)

# Generate all Redis keys at once
redis_keys = [f"latest_vulnerabilities:{org_id}" for org_id in filtered_org_ids]

# Use MGET to fetch all keys in a single operation
results = await redis_client.mget(*redis_keys)

vulnerabilities = []

# Fetch data from Redis for each organization
for org_id in filtered_org_ids:
redis_key = f"latest_vulnerabilities:{org_id}"
org_vulnerabilities = await redis_client.get(redis_key)
if org_vulnerabilities:
vulnerabilities.extend(json.loads(org_vulnerabilities))
# Process the results, skip None values
for data in results:
if data:
vulnerabilities.extend(json.loads(data))

# Limit the results to the maximum specified
vulnerabilities = sorted(vulnerabilities, key=lambda x: x["createdAt"])[
Expand All @@ -288,7 +292,7 @@ async def stats_latest_vulns(


async def stats_most_common_vulns(
filter_data, current_user, redis_client, max_results=100, filtered_org_ids=None
filter_data, current_user, redis_client, max_results=10, filtered_org_ids=None
):
"""
Retrieve the most common vulnerabilities from Elasticache filtered by user.
Expand All @@ -304,25 +308,25 @@ async def stats_most_common_vulns(
detail="No organizations found for the user with the specified filters.",
)

# Generate all Redis keys at once
redis_keys = [
f"most_common_vulnerabilities:{org_id}" for org_id in filtered_org_ids
]

# Use MGET to fetch all keys in a single operation
results = await redis_client.mget(*redis_keys)

vulnerabilities = []

# Fetch data from Redis for each organization
for org_id in filtered_org_ids:
redis_key = f"most_common_vulnerabilities:{org_id}"
org_vulnerabilities = await redis_client.get(redis_key)
if org_vulnerabilities:
vulnerabilities.extend(json.loads(org_vulnerabilities))
# Process the results, skip None values
for data in results:
if data:
vulnerabilities.extend(json.loads(data))

# Limit the results to the maximum specified
vulnerabilities = sorted(
vulnerabilities, key=lambda x: x["count"], reverse=True
)[:max_results]

if not vulnerabilities:
raise HTTPException(
status_code=404,
detail="No vulnerabilities found for the user's organizations in cache.",
)
vulnerabilities = sorted(vulnerabilities, key=lambda x: x["count"])[
:max_results
]

return vulnerabilities

Expand Down
12 changes: 11 additions & 1 deletion backend/src/xfd_django/xfd_api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ async def get_jwt_from_code(auth_code: str):
callback_url = os.getenv("REACT_APP_COGNITO_CALLBACK_URL")
client_id = os.getenv("REACT_APP_COGNITO_CLIENT_ID")
domain = os.getenv("REACT_APP_COGNITO_DOMAIN")
proxy_url = os.getenv("LZ_PROXY_URL")

scope = "openid"
authorize_token_url = f"https://{domain}/oauth2/token"
authorize_token_body = {
Expand All @@ -382,8 +384,16 @@ async def get_jwt_from_code(auth_code: str):
"Content-Type": "application/x-www-form-urlencoded",
}

# Set up proxies if PROXY_URL is defined
proxies = None
if proxy_url:
proxies = {"http": proxy_url, "https": proxy_url}

response = requests.post(
authorize_token_url, headers=headers, data=urlencode(authorize_token_body)
authorize_token_url,
headers=headers,
data=urlencode(authorize_token_body),
proxies=proxies,
)
token_response = response.json()
# Convert the id_token to bytes
Expand Down
Loading

0 comments on commit b67b43d

Please sign in to comment.