multiple accounts

This commit is contained in:
Peter Howell 2025-10-08 22:20:31 +00:00
parent d4e143e06c
commit a3093e1c8b
4 changed files with 1622 additions and 1138 deletions

View File

@ -149,6 +149,16 @@ const cy = cytoscape({
'text-halign': 'left',
'text-opacity': 0 /* hide built-in labels; we render HTML labels */
}},
{ selector: 'node[type = "account"]', style: {
'background-color': '#0b1220',
'background-opacity': 0.45,
'border-color': 'data(color)',
'border-width': 2,
'padding': '90px',
'shape': 'round-rectangle',
'text-opacity': 0,
'z-compound-depth': 'bottom'
}},
{ selector: 'node[lx]', style: { 'text-margin-x': 'data(lx)' }},
{ selector: 'node[ly]', style: { 'text-margin-y': 'data(ly)' }},
{ selector: 'node[label]', style: { 'label': 'data(label)' }},
@ -234,6 +244,38 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
const g = (graph.regions && graph.regions[region]) || {};
const elements = [];
const accountPalette = ['#7c3aed','#0ea5e9','#f97316','#10b981','#facc15','#f472b6'];
const accountMeta = new Map();
function canonicalAccountId(value, fallbackIndex){
const raw = (value ?? '').toString().trim();
return raw ? raw.replace(/\s+/g,'-') : `account-${fallbackIndex}`;
}
function pickAccountColor(index){
return accountPalette[index % accountPalette.length];
}
function registerAccount(rawId, label, color, alias){
const info = { id: rawId, label, color, nodeId: `account:${rawId}`, alias: alias || null };
accountMeta.set(rawId, info);
elements.push({ data: { id: info.nodeId, type:'account', label, color, rawId, alias: info.alias } });
return info;
}
// Accounts are optional; gather them from the top-level array and from any
// `accountId` fields present on VPC records so multiple AWS accounts can share one canvas.
const accounts = Array.isArray(graph.accounts) ? graph.accounts : [];
accounts.forEach((acct, index) => {
const rawId = canonicalAccountId(acct.id || acct.accountId || acct.alias, index + 1);
const color = acct.color || acct.colour || acct.accent || pickAccountColor(index);
const label = acct.label || acct.name || rawId;
const alias = acct.alias || acct.accountAlias || null;
registerAccount(rawId, label, color, alias);
});
function ensureAccount(raw){
if (raw == null) return null;
const canonical = canonicalAccountId(raw, accountMeta.size + 1);
if (accountMeta.has(canonical)) return accountMeta.get(canonical);
return registerAccount(canonical, canonical, pickAccountColor(accountMeta.size), null);
}
const vpcs = g.vpcs || [];
const subnets = g.subnets || [];
const sgs = g.sgs || [];
@ -241,22 +283,34 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
const rds = g.rds || [];
const lbs = g.lbs || [];
const vpcAccountMeta = new Map();
const subnetAccountMeta = new Map();
const sgById = Object.fromEntries(sgs.map(s => [s.id, s]));
// VPCs
for (const v of vpcs) {
elements.push({ data: { id: v.id, type: 'vpc', label: `${v.name}\n${v.cidr}`, cidr: v.cidr, name: v.name } });
const acctInfo = ensureAccount(v.accountId || v.account || v.account_id || null);
if (acctInfo) vpcAccountMeta.set(v.id, acctInfo);
const parent = acctInfo ? acctInfo.nodeId : undefined;
elements.push({ data: { id: v.id, type: 'vpc', label: `${v.name}\n${v.cidr}`, cidr: v.cidr, name: v.name, accountId: acctInfo ? acctInfo.id : null, accountLabel: acctInfo ? acctInfo.label : null, parent } });
}
// Subnets
for (const s of subnets) {
const label = `${(s.name||s.id)}\n${s.cidr}\n${s.az}`;
elements.push({ data: { id: s.id, type: 'subnet', label, cidr: s.cidr, az: s.az, public: !!s.public, parent: s.vpc } });
const fallbackAcct = vpcAccountMeta.get(s.vpc || '') || null;
const acctInfo = ensureAccount(s.accountId || (fallbackAcct && fallbackAcct.id) || null) || fallbackAcct;
if (acctInfo) subnetAccountMeta.set(s.id, acctInfo);
elements.push({ data: { id: s.id, type: 'subnet', label, cidr: s.cidr, az: s.az, public: !!s.public, parent: s.vpc, accountId: acctInfo ? acctInfo.id : null } });
}
// SGs
for (const sg of sgs) {
const rin = sg.rules_in || [], rout = sg.rules_out || [];
const label = `${sg.name || sg.id}\ningress: ${(rin[0]||'—')}${rin.length>1?'…':''}`;
elements.push({ data: { id: sg.id, type: 'sg', label, rules: { in: rin, out: rout } } });
const fallbackAcct = vpcAccountMeta.get(sg.vpc || '') || null;
const acctInfo = ensureAccount(sg.accountId || (fallbackAcct && fallbackAcct.id) || null) || fallbackAcct;
const parent = acctInfo ? acctInfo.nodeId : undefined;
elements.push({ data: { id: sg.id, type: 'sg', label, rules: { in: rin, out: rout }, parent, accountId: acctInfo ? acctInfo.id : null, vpc: sg.vpc || null } });
}
// SG<->SG edges based on rules referencing other SGs
function parseSgRef(rule){
@ -287,16 +341,20 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
// EC2
for (const i of ec2s) {
const label = `${i.name || i.id}\n${i.id}\n${i.type || ''}\n${i.privateIp ? ('Private IP: ' + i.privateIp) : ''}`;
const fallbackAcct = subnetAccountMeta.get(i.subnet || '') || vpcAccountMeta.get(i.vpc || '') || null;
const acctInfo = ensureAccount(i.accountId || (fallbackAcct && fallbackAcct.id) || null) || fallbackAcct;
elements.push({ data: { id: i.id, type: 'ec2', label,
name: i.name, instanceId: i.id, privateIp: i.privateIp,
publicIp: i.publicIp, state: i.state, parent: i.subnet, sgs: i.sgs || [] } });
publicIp: i.publicIp, state: i.state, parent: i.subnet, sgs: i.sgs || [], accountId: acctInfo ? acctInfo.id : null, vpc: i.vpc || null } });
for (const sgid of (i.sgs || [])) if (sgById[sgid]) elements.push({ data: { id:`sg-${sgid}->${i.id}`, source: sgid, target: i.id, label:'attached', class:'sg-attach' }});
}
// RDS
for (const db of rds) {
const parentSubnet = (db.subnetGroup && db.subnetGroup[0]) || null;
const label = `${db.engine}\n${db.port}\n${db.publiclyAccessible?'public':'private'}`;
elements.push({ data: { id: db.id, type:'rds', label, engine:db.engine, port:db.port, publiclyAccessible:!!db.publiclyAccessible, parent: parentSubnet, sgs: db.sgs || [] }});
const fallbackAcct = subnetAccountMeta.get(parentSubnet || '') || null;
const acctInfo = ensureAccount(db.accountId || (fallbackAcct && fallbackAcct.id) || null) || fallbackAcct;
elements.push({ data: { id: db.id, type:'rds', label, engine:db.engine, port:db.port, publiclyAccessible:!!db.publiclyAccessible, parent: parentSubnet, sgs: db.sgs || [], accountId: acctInfo ? acctInfo.id : null } });
for (const sgid of (db.sgs || [])) if (sgById[sgid]) elements.push({ data: { id:`sg-${sgid}->${db.id}`, source: sgid, target: db.id, label:'attached', class:'sg-attach' }});
}
// LBs
@ -304,7 +362,9 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
const parent = (lb.subnets && lb.subnets[0]) || (subnets[0] && subnets[0].id) || null;
const lstText = (lb.listeners || []).map(L => L.port).join(',');
const label = `${lb.id}\nlisteners: ${lstText || '—'}\n${lb.scheme}`;
elements.push({ data: { id: lb.id, type: (lb.type==='network'?'nlb':'alb'), label, scheme: lb.scheme, listeners: lb.listeners || [], parent, sgs: lb.securityGroups || [], dns: lb.dns } });
const fallbackAcct = subnetAccountMeta.get(parent || '') || null;
const acctInfo = ensureAccount(lb.accountId || (fallbackAcct && fallbackAcct.id) || null) || fallbackAcct;
elements.push({ data: { id: lb.id, type: (lb.type==='network'?'nlb':'alb'), label, scheme: lb.scheme, listeners: lb.listeners || [], parent, sgs: lb.securityGroups || [], dns: lb.dns, accountId: acctInfo ? acctInfo.id : null } });
for (const sgid of (lb.securityGroups || [])) if (sgById[sgid]) elements.push({ data: { id:`sg-${sgid}->${lb.id}`, source: sgid, target: lb.id, label:'attached', class:'sg-attach' }});
const tgs = lb.targetGroups || []; const targetToPort = {};
for (const tg of tgs) for (const t of (tg.targets || [])) targetToPort[t] = tg.port || targetToPort[t] || '';
@ -335,13 +395,37 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
default: return '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="5" y="5" width="14" height="14" rx="2" stroke-width="1.5"/></svg>';
}
}
function vpnBadge(name){ return (name && /vpn/i.test(name)) ? '<span class="badge">VPN</span>' : ''; }
function vpnBadge(name){ return (name && /vpn/i.test(name)) ? '<span class="badge type-badge type-vpn">VPN</span>' : ''; }
function typeBadge(type){
switch(type){
case 'nat': return '<span class="badge type-badge type-nat">NAT</span>';
case 'igw': return '<span class="badge type-badge type-igw">IGW</span>';
case 'endpoint':
case 'vpce':
case 'gateway-endpoint':
return '<span class="badge type-badge type-endpoint">VPCE</span>';
case 'sg': return '<span class="badge type-badge">SG</span>';
default: return '';
}
}
function statusDot(state){ const cls=(state==='running')?'run':(state==='stopped'?'stop':'unk'); const txt=state||'unknown'; return `<span class="status"><i class="dot ${cls}"></i>${escapeHtml(txt)}</span>`; }
function sgRuleCount(d){ const rin=(d.rules&&d.rules.in)?d.rules.in.length:0; const rout=(d.rules&&d.rules.out)?d.rules.out.length:0; return rin+rout; }
// Attach HTML labels for SG/ALB/NLB/RDS/EC2 centered inside nodes and Subnet at top-left
if (cy.nodeHtmlLabel) {
cy.nodeHtmlLabel([
{
query:'node[type = "account"]',
halign:'left', valign:'top', halignBox:'left', valignBox:'top',
tpl: function(d){
const accent=d.color||'#38bdf8';
const name=d.label||(d.rawId||d.id||'account');
return `<div class="account-card" style="--accent:${escapeHtml(accent)}">
<span class="account-dot"></span>
<span class="account-name">${escapeHtml(name)}</span>
</div>`;
}
},
{
query:'node[type = "sg"], node[type = "alb"], node[type = "nlb"], node[type = "rds"], node[type = "ec2"]',
halign:'center', valign:'center', halignBox:'center', valignBox:'center',
@ -361,12 +445,73 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
if (priv) lines.push(`<div class="n-sub">Private IP: ${escapeHtml(priv)}</div>`);
if (pub) lines.push(`<div class="n-sub">Public: ${escapeHtml(pub)}</div>`);
}
const badge=vpnBadge(name);
const badges=[];
const vpnChip=vpnBadge(name); if (vpnChip) badges.push(vpnChip);
const typeChip=typeBadge(type); if (typeChip) badges.push(typeChip);
const badgeHtml=badges.join('');
return `<div class="ncard n-${type}" style="background:none;border:none;box-shadow:none;padding:0;margin:0;display:block;">
<div class="n-title">${escapeHtml(name)}${badge}</div>
<div class="n-title" style="display:flex;align-items:center;gap:8px;">${iconSvg(type)}${escapeHtml(name)}${badgeHtml}</div>
${lines.join('')}
</div>`;
}
},
{
query:'node[type = "alb"], node[type = "nlb"]',
halign:'center', valign:'center', halignBox:'center', valignBox:'center',
tpl: function(d){
const icon = iconSvg(d.type === 'nlb' ? 'nlb' : 'alb');
const name = (d.label || '').split('\n')[0] || d.id;
const dns = d.dns ? `<div class="n-sub">${escapeHtml(d.dns)}</div>` : '';
const listeners = Array.isArray(d.listeners) ? d.listeners.map(L => L.port).join(', ') : '';
const listenerLine = listeners ? `<div class="n-sub">listeners: ${escapeHtml(listeners)}</div>` : '';
const badges=[];
const typeChip=typeBadge(d.type);
if (typeChip) badges.push(typeChip);
const badgeHtml=badges.join('');
return `<div class="ncard n-${d.type}" style="background:#fee2e2;border:1px solid #ef4444;box-shadow:none;padding:10px 12px;border-radius:10px;">
<div class="n-title" style="display:flex;align-items:center;gap:8px;">
${icon}
${escapeHtml(name)}${badgeHtml}
</div>
${dns}
${listenerLine}
</div>`;
}
},
{
query:'node[type = "sg"]',
halign:'center', valign:'center', halignBox:'center', valignBox:'center',
tpl: function(d){
const rules = sgRuleCount(d);
const badges=[];
const typeChip=typeBadge('sg'); if (typeChip) badges.push(typeChip);
const badgeHtml=badges.join('');
return `<div class="ncard n-sg" style="background:#fff7ed;border:1px solid #f59e0b;box-shadow:none;padding:10px 12px;border-radius:10px;">
<div class="n-title" style="display:flex;align-items:center;gap:8px;">
${iconSvg('sg')}
${escapeHtml(d.name || d.label || d.id)}${badgeHtml}
</div>
<div class="n-sub">${escapeHtml(`${rules} rule${rules===1?'':'s'}`)}</div>
</div>`;
}
},
{
query:'node[type = "nat"], node[type = "igw"], node[type = "vpn"]',
halign:'center', valign:'center', halignBox:'center', valignBox:'center',
tpl: function(d){
const name=d.name||(d.label||'').split('\n')[0]||d.id;
const icon = iconSvg(d.type);
const badges=[];
const vpnChip=vpnBadge(name); if (vpnChip) badges.push(vpnChip);
const typeChip=typeBadge(d.type); if (typeChip) badges.push(typeChip);
const badgeHtml=badges.join('');
return `<div class="ncard n-${d.type}" style="background:#ffe4e6;border:1px solid #fb7185;box-shadow:none;padding:8px 10px;border-radius:10px;">
<div class="n-title" style="display:flex;align-items:center;gap:8px;">
${icon}
${escapeHtml(name)}${badgeHtml}
</div>
</div>`;
}
},
{
query:'node[type = "subnet"]',
@ -496,12 +641,13 @@ async function loadGraphAndBuildElements(region = 'us-west-1') {
if(!ele){ $details.textContent=''; return; }
const d=ele.data(); const base={ id:d.id, type:d.type, label:d.label }; let extra={};
switch(d.type){
case 'vpc': extra={ cidr:d.cidr, name:d.name }; break;
case 'vpc': extra={ cidr:d.cidr, name:d.name, accountId:d.accountId, accountLabel:d.accountLabel }; break;
case 'account': extra={ label:d.label, color:d.color, rawId:d.rawId, alias:d.alias }; break;
case 'subnet': extra={ cidr:d.cidr, az:d.az, public:d.public }; break;
case 'alb': extra={ scheme:d.scheme, dns:d.dns, listeners:d.listeners, sgs:d.sgs }; break;
case 'ec2': extra={ name:d.name, instanceId:d.instanceId, privateIp:d.privateIp, publicIp:d.publicIp, state:d.state, sgs:d.sgs }; break;
case 'rds': extra={ engine:d.engine, port:d.port, publiclyAccessible:d.publiclyAccessible, sgs:d.sgs }; break;
case 'sg': extra={ inbound_sorted: sortRules(d.rules && d.rules.in), outbound_sorted: sortRules(d.rules && d.rules.out) }; break;
case 'alb': extra={ scheme:d.scheme, dns:d.dns, listeners:d.listeners, sgs:d.sgs, accountId:d.accountId }; break;
case 'ec2': extra={ name:d.name, instanceId:d.instanceId, privateIp:d.privateIp, publicIp:d.publicIp, state:d.state, sgs:d.sgs, accountId:d.accountId }; break;
case 'rds': extra={ engine:d.engine, port:d.port, publiclyAccessible:d.publiclyAccessible, sgs:d.sgs, accountId:d.accountId }; break;
case 'sg': extra={ inbound_sorted: sortRules(d.rules && d.rules.in), outbound_sorted: sortRules(d.rules && d.rules.out), accountId: d.accountId }; break;
default: extra=d; break;
}
$details.textContent=JSON.stringify({ ...base, ...extra }, null, 2);

305
gather.py
View File

@ -1,114 +1,277 @@
#!/usr/bin/env python3
import boto3, json, os, sys
"""
Fetch AWS VPC-related metadata and emit a Cytoscape-friendly `graph.json` file.
Adds support for multiple AWS accounts by letting you specify one or more AWS CLI
profiles. Each account is tagged in the output so the front-end can group VPCs
per account.
"""
import argparse
import json
from collections import defaultdict
import boto3
from botocore.exceptions import ClientError
RESOURCE_KEYS = ['vpcs', 'subnets', 'sgs', 'enis', 'ec2', 'lbs', 'rds', 'exposures']
ACCOUNT_COLORS = ['#7c3aed', '#0ea5e9', '#f97316', '#10b981', '#facc15', '#f472b6']
def name_tag(tags):
return next((t['Value'] for t in (tags or []) if t['Key'] == 'Name'), None)
def collect(region):
s = boto3.Session(region_name=region)
ec2, elb, rds = s.client('ec2'), s.client('elbv2'), s.client('rds')
out = {k:[] for k in ['vpcs','subnets','sgs','enis','ec2','lbs','rds','exposures']}
# VPCs, Subnets, SGs, ENIs, Instances
def empty_region():
return {k: [] for k in RESOURCE_KEYS}
def resolve_account(session, profile_hint=None):
sts = session.client('sts')
identity = sts.get_caller_identity()
account_id = identity['Account']
alias = None
try:
iam = session.client('iam')
aliases = iam.list_account_aliases().get('AccountAliases', [])
alias = aliases[0] if aliases else None
except ClientError:
alias = None
label = alias or profile_hint or account_id
return {'id': account_id, 'label': label, 'alias': alias}
def collect(session, account):
region = session.region_name
out = empty_region()
ec2 = session.client('ec2')
elb = session.client('elbv2')
rds = session.client('rds')
vpcs = ec2.describe_vpcs()['Vpcs']
subnets = ec2.describe_subnets()['Subnets']
sgs = ec2.describe_security_groups()['SecurityGroups']
enis = ec2.describe_network_interfaces()['NetworkInterfaces']
resv = ec2.describe_instances()['Reservations']
reservations = ec2.describe_instances()['Reservations']
for v in vpcs:
out['vpcs'].append({'id':v['VpcId'],'cidr':v['CidrBlock'],'name':name_tag(v.get('Tags')) or v['VpcId']})
out['vpcs'].append({
'id': v['VpcId'],
'cidr': v['CidrBlock'],
'name': name_tag(v.get('Tags')) or v['VpcId'],
'accountId': account['id']
})
for sn in subnets:
out['subnets'].append({
'id':sn['SubnetId'],'vpc':sn['VpcId'],'cidr':sn['CidrBlock'],
'az':sn['AvailabilityZone'],'public':sn.get('MapPublicIpOnLaunch',False)
'id': sn['SubnetId'],
'vpc': sn['VpcId'],
'cidr': sn['CidrBlock'],
'az': sn['AvailabilityZone'],
'public': sn.get('MapPublicIpOnLaunch', False),
'accountId': account['id']
})
for g in sgs:
def flatten(perms):
r=[]
for p in perms:
proto=p.get('IpProtocol'); frm=p.get('FromPort'); to=p.get('ToPort')
port = f"{frm}" if frm==to else f"{frm}-{to}" if frm is not None else "all"
for ipr in p.get('IpRanges',[]) + p.get('Ipv6Ranges',[]):
def flatten_rules(perms):
rules = []
for perm in perms:
proto = perm.get('IpProtocol')
frm = perm.get('FromPort')
to = perm.get('ToPort')
if frm is None:
port = 'all'
elif frm == to:
port = f"{frm}"
else:
port = f"{frm}-{to}"
for ipr in perm.get('IpRanges', []) + perm.get('Ipv6Ranges', []):
cidr = ipr.get('CidrIp') or ipr.get('CidrIpv6')
r.append(f"{proto} {port} from {cidr}")
for sgr in p.get('UserIdGroupPairs',[]):
r.append(f"{proto} {port} from {sgr['GroupId']}")
return r
out['sgs'].append({'id':g['GroupId'],'name':g.get('GroupName'),
'rules_in':flatten(g.get('IpPermissions',[])),
'rules_out':flatten(g.get('IpPermissionsEgress',[]))})
rules.append(f"{proto} {port} from {cidr}")
for sgr in perm.get('UserIdGroupPairs', []):
rules.append(f"{proto} {port} from {sgr['GroupId']}")
return rules
for g in sgs:
out['sgs'].append({
'id': g['GroupId'],
'name': g.get('GroupName'),
'vpc': g.get('VpcId'),
'accountId': account['id'],
'rules_in': flatten_rules(g.get('IpPermissions', [])),
'rules_out': flatten_rules(g.get('IpPermissionsEgress', []))
})
for ni in enis:
assoc = ni.get('Association', {})
out['enis'].append({
'id':ni['NetworkInterfaceId'],'subnet':ni['SubnetId'],
'id': ni['NetworkInterfaceId'],
'subnet': ni['SubnetId'],
'vpc': ni.get('VpcId'),
'sgs': [sg['GroupId'] for sg in ni.get('Groups', [])],
'privateIp': ni.get('PrivateIpAddress'),
'publicIp':assoc.get('PublicIp')
})
for r in resv:
for i in r['Instances']:
n = name_tag(i.get('Tags')) or i['InstanceId']
eni = (i.get('NetworkInterfaces') or [{}])[0]
out['ec2'].append({
'id': i['InstanceId'],
'name': n,
'type': i['InstanceType'],
'privateIp': i.get('PrivateIpAddress'),
'publicIp': i.get('PublicIpAddress'), # shows on label
'state': i.get('State', {}).get('Name', 'unknown'), # running/stopped
'sgs': [g['GroupId'] for g in i.get('SecurityGroups',[])],
'subnet': i.get('SubnetId')
'publicIp': assoc.get('PublicIp'),
'accountId': account['id']
})
for reservation in reservations:
for inst in reservation['Instances']:
inst_name = name_tag(inst.get('Tags')) or inst['InstanceId']
out['ec2'].append({
'id': inst['InstanceId'],
'name': inst_name,
'type': inst['InstanceType'],
'privateIp': inst.get('PrivateIpAddress'),
'publicIp': inst.get('PublicIpAddress'),
'state': inst.get('State', {}).get('Name', 'unknown'),
'sgs': [g['GroupId'] for g in inst.get('SecurityGroups', [])],
'subnet': inst.get('SubnetId'),
'vpc': inst.get('VpcId'),
'accountId': account['id']
})
# Load balancers (ALB/NLB), listeners, target groups/targets
try:
lbs = elb.describe_load_balancers()['LoadBalancers']
except elb.exceptions.UnsupportedFeatureException:
except (ClientError, elb.exceptions.UnsupportedFeatureException):
lbs = []
for lb in lbs:
lbid = lb['LoadBalancerName']
lb_id = lb['LoadBalancerName']
try:
listeners = elb.describe_listeners(LoadBalancerArn=lb['LoadBalancerArn'])['Listeners']
lst = [{'proto':L['Protocol'],'port':L['Port']} for L in listeners]
except ClientError:
listeners = []
listener_meta = [{'proto': L['Protocol'], 'port': L['Port']} for L in listeners]
try:
tgs = elb.describe_target_groups(LoadBalancerArn=lb['LoadBalancerArn'])['TargetGroups']
tga=[]
except ClientError:
tgs = []
target_groups = []
all_targets = []
for tg in tgs:
th = elb.describe_target_health(TargetGroupArn=tg['TargetGroupArn'])['TargetHealthDescriptions']
targets=[thd['Target']['Id'] for thd in th]
tga.append({'port':tg.get('Port'),'targets':targets})
try:
health = elb.describe_target_health(TargetGroupArn=tg['TargetGroupArn'])['TargetHealthDescriptions']
except ClientError:
health = []
targets = [desc['Target']['Id'] for desc in health]
target_groups.append({'port': tg.get('Port'), 'targets': targets})
all_targets.extend(targets)
out['lbs'].append({
'id': lbid,
'id': lb_id,
'scheme': lb['Scheme'],
'type': lb['Type'],
'dns': lb.get('DNSName'), # add me (handy on the label)
'subnets': [z['SubnetId'] for z in lb.get('AvailabilityZones',[])],
'dns': lb.get('DNSName'),
'subnets': [az['SubnetId'] for az in lb.get('AvailabilityZones', [])],
'securityGroups': lb.get('SecurityGroups', []),
'listeners': lst, 'targetGroups': tga
'listeners': listener_meta,
'targetGroups': target_groups,
'accountId': account['id']
})
if lb['Scheme'] == 'internet-facing':
for listener in listener_meta:
out['exposures'].append({
'surface': f"{lb_id}:{listener['port']}",
'world_open': True,
'via': lb['Type'].upper(),
'to': [
f"{t}:{next((tg['port'] for tg in target_groups if t in tg['targets']), None)}"
for t in all_targets
],
'accountId': account['id'],
'region': region
})
# Simple exposure: internet-facing LB listeners are public surfaces
if lb['Scheme']=='internet-facing':
for L in lst:
out['exposures'].append({'surface':f"{lbid}:{L['port']}",
'world_open':True,'via':lb['Type'].upper(),
'to': [f"{t}:{next((tg['port'] for tg in tga if t in tg['targets']),None)}" for t in sum([tg['targets'] for tg in tga],[])]})
# RDS
for db in rds.describe_db_instances()['DBInstances']:
out['rds'].append({
'id':db['DBInstanceIdentifier'],'engine':db['Engine'],
'port':db['DbInstancePort'] if 'DbInstancePort' in db else db.get('Endpoint',{}).get('Port'),
'id': db['DBInstanceIdentifier'],
'engine': db['Engine'],
'port': db.get('DbInstancePort') or db.get('Endpoint', {}).get('Port'),
'publiclyAccessible': db.get('PubliclyAccessible', False),
'sgs':[v['VpcSecurityGroupId'] for v in db.get('VpcSecurityGroups',[])],
'subnetGroup':[s['SubnetIdentifier'] for s in db.get('DBSubnetGroup',{}).get('Subnets',[])]
'sgs': [sg['VpcSecurityGroupId'] for sg in db.get('VpcSecurityGroups', [])],
'subnetGroup': [s['SubnetIdentifier'] for s in db.get('DBSubnetGroup', {}).get('Subnets', [])],
'accountId': account['id']
})
return out
def main():
regions = sys.argv[1:] or [boto3.Session().region_name or 'us-west-1']
graph = {'regions':{}}
for r in regions:
graph['regions'][r] = collect(r)
with open('graph.json','w') as f: json.dump(graph, f, indent=2)
print('Wrote graph.json for regions:', ', '.join(regions))
if __name__=='__main__': main()
def parse_args():
parser = argparse.ArgumentParser(description='Gather AWS VPC graph data into graph.json')
parser.add_argument('regions', nargs='*', help='AWS regions to scan (defaults to your configured region)')
parser.add_argument('-r', '--region', dest='regions_opt', action='append', help='Additional region to scan (repeatable)')
parser.add_argument('-p', '--profile', dest='profiles', action='append', help='AWS CLI profile to use (repeatable)')
parser.add_argument('--account-label', dest='account_labels', action='append', help='Friendly label per profile (matches order of --profile)')
parser.add_argument('-o', '--output', default='graph.json', help='Output file (default: graph.json)')
return parser
def main():
parser = parse_args()
args = parser.parse_args()
regions = []
if args.regions:
regions.extend(args.regions)
if args.regions_opt:
regions.extend(args.regions_opt)
if not regions:
default_region = boto3.Session().region_name
if not default_region:
parser.error('No regions specified and no default region configured.')
regions = [default_region]
regions = sorted(set(regions))
profiles = args.profiles or [None]
account_label_overrides = args.account_labels or []
graph_accounts = []
account_index = {}
region_data = defaultdict(empty_region)
for idx, profile in enumerate(profiles):
profile_label_hint = account_label_overrides[idx] if idx < len(account_label_overrides) else profile
for region in regions:
session = boto3.Session(profile_name=profile, region_name=region)
account = resolve_account(session, profile_label_hint)
account_id = account['id']
if account_id not in account_index:
color = ACCOUNT_COLORS[len(account_index) % len(ACCOUNT_COLORS)]
entry = {
'id': account_id,
'label': account['label'],
'alias': account['alias'],
'profile': profile,
'color': color
}
graph_accounts.append(entry)
account_index[account_id] = entry
else:
entry = account_index[account_id]
data = collect(session, entry)
bucket = region_data[region]
for key in RESOURCE_KEYS:
bucket[key].extend(data[key])
graph = {
'accounts': graph_accounts,
'regions': {region: bucket for region, bucket in region_data.items()}
}
with open(args.output, 'w') as f:
json.dump(graph, f, indent=2)
region_list = ', '.join(regions)
profile_list = ', '.join(p or 'default' for p in profiles)
print(f"Wrote {args.output} for regions: {region_list} (profiles: {profile_list})")
if __name__ == '__main__':
main()

View File

@ -1,31 +1,48 @@
{
"accounts": [
{
"id": "acct-shared",
"label": "Shared Services",
"color": "#7c3aed"
},
{
"id": "acct-prod",
"label": "Production Apps",
"color": "#0ea5e9"
}
],
"regions": {
"us-west-1": {
"vpcs": [
{
"id": "vpc-87832de2",
"cidr": "10.4.0.0/16",
"name": "vault.vpc.01"
"name": "vault.vpc.01",
"accountId": "acct-shared"
},
{
"id": "vpc-c84eabad",
"cidr": "172.31.0.0/16",
"name": "vpc-c84eabad"
"name": "vpc-c84eabad",
"accountId": "acct-prod"
},
{
"id": "vpc-331ad056",
"cidr": "10.3.0.0/16",
"name": "efw.vpc.02"
"name": "efw.vpc.02",
"accountId": "acct-prod"
},
{
"id": "vpc-afe722ca",
"cidr": "10.2.0.0/16",
"name": "efw.vpc.01"
"name": "efw.vpc.01",
"accountId": "acct-prod"
},
{
"id": "vpc-03f799f47b766798c",
"cidr": "10.10.0.0/16",
"name": "ALB-VPC"
"name": "ALB-VPC",
"accountId": "acct-shared"
}
],
"subnets": [
@ -34,98 +51,112 @@
"vpc": "vpc-331ad056",
"cidr": "10.3.0.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-prod"
},
{
"id": "subnet-f73afeae",
"vpc": "vpc-331ad056",
"cidr": "10.3.1.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-prod"
},
{
"id": "subnet-ebc4188e",
"vpc": "vpc-afe722ca",
"cidr": "10.2.1.0/24",
"az": "us-west-1c",
"public": false
"public": false,
"accountId": "acct-prod"
},
{
"id": "subnet-005bfd58e719e031f",
"vpc": "vpc-87832de2",
"cidr": "10.4.11.0/24",
"az": "us-west-1c",
"public": false
"public": false,
"accountId": "acct-shared"
},
{
"id": "subnet-035f81142068bf961",
"vpc": "vpc-03f799f47b766798c",
"cidr": "10.10.2.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-shared"
},
{
"id": "subnet-d137fd88",
"vpc": "vpc-87832de2",
"cidr": "10.4.0.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-shared"
},
{
"id": "subnet-e98e088c",
"vpc": "vpc-331ad056",
"cidr": "10.3.2.0/24",
"az": "us-west-1c",
"public": false
"public": false,
"accountId": "acct-prod"
},
{
"id": "subnet-08e40dcdc103438b8",
"vpc": "vpc-87832de2",
"cidr": "10.4.2.0/24",
"az": "us-west-1c",
"public": false
"public": false,
"accountId": "acct-shared"
},
{
"id": "subnet-0cb2aa7457413f5fe",
"vpc": "vpc-03f799f47b766798c",
"cidr": "10.10.3.0/24",
"az": "us-west-1c",
"public": true
"public": true,
"accountId": "acct-shared"
},
{
"id": "subnet-247a9341",
"vpc": "vpc-c84eabad",
"cidr": "172.31.16.0/20",
"az": "us-west-1c",
"public": true
"public": true,
"accountId": "acct-prod"
},
{
"id": "subnet-34173f72",
"vpc": "vpc-c84eabad",
"cidr": "172.31.0.0/20",
"az": "us-west-1b",
"public": true
"public": true,
"accountId": "acct-prod"
},
{
"id": "subnet-05b5dcdc91a8d3da2",
"vpc": "vpc-87832de2",
"cidr": "10.4.10.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-shared"
},
{
"id": "subnet-16594550",
"vpc": "vpc-afe722ca",
"cidr": "10.2.2.0/24",
"az": "us-west-1b",
"public": false
"public": false,
"accountId": "acct-prod"
},
{
"id": "subnet-051f6d705e415834d",
"vpc": "vpc-03f799f47b766798c",
"cidr": "10.10.1.0/24",
"az": "us-west-1b",
"public": true
"public": true,
"accountId": "acct-shared"
}
],
"sgs": [
@ -151,7 +182,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "sg-00600deefd8e47cd0",
@ -161,7 +194,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-da75adbf",
@ -176,7 +210,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "sg-c05a2ba5",
@ -186,7 +222,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-0c00315bbb744b876",
@ -196,7 +233,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-495ab32c",
@ -208,7 +246,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-0f2465c4421c2d5c2",
@ -219,7 +258,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-8b746aec",
@ -233,7 +273,8 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-shared"
},
{
"id": "sg-0a872ac3a6d9132a6",
@ -245,7 +286,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "sg-076d2ea1df0054074",
@ -256,7 +299,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "sg-b45a2bd1",
@ -287,7 +332,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "sg-4235ee27",
@ -298,7 +345,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "sg-04167d2e18ba15e45",
@ -308,7 +357,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "sg-06348763",
@ -353,7 +404,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "sg-08fb4b412f90d5913",
@ -361,7 +414,8 @@
"rules_in": [],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"accountId": "acct-prod"
},
{
"id": "sg-09621eafa81d9554d",
@ -372,7 +426,9 @@
],
"rules_out": [
"-1 None from 0.0.0.0/0"
]
],
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
}
],
"enis": [
@ -383,7 +439,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.216",
"publicIp": "52.8.79.80"
"publicIp": "52.8.79.80",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0c6e3761c14140e52",
@ -392,7 +450,9 @@
"sg-076d2ea1df0054074"
],
"privateIp": "10.10.1.212",
"publicIp": "50.18.175.227"
"publicIp": "50.18.175.227",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "eni-6a099b32",
@ -401,7 +461,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.207",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-072ff2545f56240a7",
@ -410,7 +472,9 @@
"sg-4235ee27"
],
"privateIp": "10.2.2.16",
"publicIp": null
"publicIp": null,
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "eni-064252b93ca105b7c",
@ -419,7 +483,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.10.82",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-08d57de7981ae0020",
@ -429,7 +495,9 @@
"sg-0a872ac3a6d9132a6"
],
"privateIp": "10.10.2.106",
"publicIp": null
"publicIp": null,
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "eni-00feca9ca48c7ae0c",
@ -438,7 +506,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.10.141",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-8041e3da",
@ -447,7 +517,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.201",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-9d73dbc7",
@ -456,7 +528,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.98",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-04237833786a67013",
@ -465,7 +539,9 @@
"sg-0a872ac3a6d9132a6"
],
"privateIp": "10.10.1.166",
"publicIp": "52.53.243.75"
"publicIp": "52.53.243.75",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "eni-084002309358bb545",
@ -474,7 +550,9 @@
"sg-06348763"
],
"privateIp": "10.3.1.134",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0827085d571dccab7",
@ -483,7 +561,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.0.221",
"publicIp": "52.53.117.111"
"publicIp": "52.53.117.111",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-0e844b9d718f3daa0",
@ -492,7 +572,9 @@
"sg-642cf701"
],
"privateIp": "10.2.2.226",
"publicIp": "52.8.26.159"
"publicIp": "52.8.26.159",
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "eni-0d6f412450d0e082b",
@ -501,7 +583,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.63",
"publicIp": "52.8.7.0"
"publicIp": "52.8.7.0",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-05788a45421ce580f",
@ -510,7 +594,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.0.197",
"publicIp": "52.8.75.57"
"publicIp": "52.8.75.57",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-0775830b9669fa479",
@ -519,7 +605,9 @@
"sg-642cf701"
],
"privateIp": "10.2.2.18",
"publicIp": "54.153.101.192"
"publicIp": "54.153.101.192",
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "eni-0f54a2ee3a5237ae1",
@ -528,7 +616,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.111",
"publicIp": "54.153.3.41"
"publicIp": "54.153.3.41",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-08eb3a38264ccd212",
@ -537,7 +627,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.73",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0afbe7eb22dd6e8e3",
@ -546,7 +638,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.112",
"publicIp": "13.52.49.251"
"publicIp": "13.52.49.251",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-018923614c623ef74",
@ -555,7 +649,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.122",
"publicIp": "52.8.85.37"
"publicIp": "52.8.85.37",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0f310dd2e36654ed7",
@ -564,14 +660,18 @@
"sg-06348763"
],
"privateIp": "10.3.0.120",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-00172626263229e1a",
"subnet": "subnet-051f6d705e415834d",
"sgs": [],
"privateIp": "10.10.1.37",
"publicIp": "13.57.111.5"
"publicIp": "13.57.111.5",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "eni-0893189c99a73d4f2",
@ -580,14 +680,18 @@
"sg-06348763"
],
"privateIp": "10.3.0.167",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0d0b9801f1e66ec76",
"subnet": "subnet-d137fd88",
"sgs": [],
"privateIp": "10.4.0.246",
"publicIp": "184.169.224.203"
"publicIp": "184.169.224.203",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-b574dcef",
@ -596,7 +700,9 @@
"sg-642cf701"
],
"privateIp": "10.2.2.48",
"publicIp": null
"publicIp": null,
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "eni-0080f66b73a68d1d1",
@ -605,7 +711,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.0.117",
"publicIp": "52.8.219.246"
"publicIp": "52.8.219.246",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-02075fc7f6b5ed6f3",
@ -614,7 +722,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.211",
"publicIp": "13.57.152.11"
"publicIp": "13.57.152.11",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-026ab4825bc4dfb8b",
@ -623,7 +733,9 @@
"sg-06348763"
],
"privateIp": "10.3.0.154",
"publicIp": null
"publicIp": null,
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "eni-0ac39f75ad7dc0709",
@ -632,7 +744,9 @@
"sg-076d2ea1df0054074"
],
"privateIp": "10.10.3.88",
"publicIp": "52.52.1.145"
"publicIp": "52.52.1.145",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "eni-0015cacdc10d467e8",
@ -641,7 +755,9 @@
"sg-09621eafa81d9554d"
],
"privateIp": "10.4.11.198",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-039a605986c15dabf",
@ -650,7 +766,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.11.166",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-01f9242eb64928de8",
@ -659,7 +777,9 @@
"sg-09621eafa81d9554d"
],
"privateIp": "10.4.11.139",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-09a54855abf31a012",
@ -668,7 +788,9 @@
"sg-b45a2bd1"
],
"privateIp": "10.4.11.69",
"publicIp": null
"publicIp": null,
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "eni-0787462fd9a1d1b6d",
@ -677,7 +799,9 @@
"sg-da75adbf"
],
"privateIp": "10.2.1.156",
"publicIp": "54.241.122.239"
"publicIp": "54.241.122.239",
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
}
],
"ec2": [
@ -690,7 +814,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-0997a73b08f6e5862",
@ -701,7 +827,9 @@
"sgs": [
"sg-642cf701"
],
"subnet": "subnet-16594550"
"subnet": "subnet-16594550",
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "i-0c8100e3460fa8fd0",
@ -712,7 +840,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-041021cd89e15282c",
@ -723,7 +853,9 @@
"sgs": [
"sg-da75adbf"
],
"subnet": "subnet-ebc4188e"
"subnet": "subnet-ebc4188e",
"vpc": "vpc-afe722ca",
"accountId": "acct-prod"
},
{
"id": "i-073b97cbda2b200c3",
@ -734,7 +866,9 @@
"sgs": [
"sg-b45a2bd1"
],
"subnet": "subnet-d137fd88"
"subnet": "subnet-d137fd88",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "i-0669f35ab2d0fc444",
@ -745,7 +879,9 @@
"sgs": [
"sg-b45a2bd1"
],
"subnet": "subnet-d137fd88"
"subnet": "subnet-d137fd88",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "i-01b3f3cd57976bdf3",
@ -756,7 +892,9 @@
"sgs": [
"sg-b45a2bd1"
],
"subnet": "subnet-d137fd88"
"subnet": "subnet-d137fd88",
"vpc": "vpc-87832de2",
"accountId": "acct-shared"
},
{
"id": "i-0272763b46610ac1b",
@ -767,7 +905,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-0c82adf476c7c5e32",
@ -778,7 +918,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-0636fd0b033c9b32a",
@ -789,7 +931,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-09241599c2590b66a",
@ -800,7 +944,9 @@
"sgs": [
"sg-06348763"
],
"subnet": "subnet-260ee27f"
"subnet": "subnet-260ee27f",
"vpc": "vpc-331ad056",
"accountId": "acct-prod"
},
{
"id": "i-0d7f643cb9d960645",
@ -811,7 +957,9 @@
"sgs": [
"sg-0a872ac3a6d9132a6"
],
"subnet": "subnet-051f6d705e415834d"
"subnet": "subnet-051f6d705e415834d",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
},
{
"id": "i-082b27477bbe6d8b5",
@ -823,7 +971,9 @@
"sg-04167d2e18ba15e45",
"sg-0a872ac3a6d9132a6"
],
"subnet": "subnet-035f81142068bf961"
"subnet": "subnet-035f81142068bf961",
"vpc": "vpc-03f799f47b766798c",
"accountId": "acct-shared"
}
],
"lbs": [
@ -855,7 +1005,8 @@
"i-082b27477bbe6d8b5"
]
}
]
],
"accountId": "acct-shared"
}
],
"rds": [
@ -870,7 +1021,8 @@
"subnetGroup": [
"subnet-e98e088c",
"subnet-f73afeae"
]
],
"accountId": "acct-prod"
},
{
"id": "logon-db-02",
@ -883,7 +1035,8 @@
"subnetGroup": [
"subnet-16594550",
"subnet-ebc4188e"
]
],
"accountId": "acct-prod"
},
{
"id": "vault-db-production-v2",
@ -896,7 +1049,8 @@
"subnetGroup": [
"subnet-005bfd58e719e031f",
"subnet-05b5dcdc91a8d3da2"
]
],
"accountId": "acct-shared"
},
{
"id": "vault-db-staging-v2",
@ -909,7 +1063,8 @@
"subnetGroup": [
"subnet-005bfd58e719e031f",
"subnet-05b5dcdc91a8d3da2"
]
],
"accountId": "acct-shared"
}
],
"exposures": [
@ -919,7 +1074,9 @@
"via": "APPLICATION",
"to": [
"i-082b27477bbe6d8b5:8080"
]
],
"accountId": "acct-shared",
"region": "us-west-1"
},
{
"surface": "NodeAppALB:443",
@ -927,7 +1084,9 @@
"via": "APPLICATION",
"to": [
"i-082b27477bbe6d8b5:8080"
]
],
"accountId": "acct-shared",
"region": "us-west-1"
}
]
}

View File

@ -1,161 +1,177 @@
{
"nodes": {
"account:acct-shared": {
"position": {
"x": 2383.6718307806645,
"y": 1142.0623550975815
},
"lx": 0,
"ly": 0
},
"account:acct-prod": {
"position": {
"x": -1257.3436368163789,
"y": 1751.2282249012555
},
"lx": 0,
"ly": 0
},
"vpc-87832de2": {
"position": {
"x": 756.8840099911947,
"y": 1038.4416407451458
"x": 2622.379364533625,
"y": 792.8749925626238
},
"lx": 77.51854351992131,
"ly": 139.5230395934425
},
"vpc-c84eabad": {
"position": {
"x": -922.864188724591,
"y": 204.9358257138463
"x": -1422.5355956832366,
"y": 280.0140805664038
},
"lx": 0,
"ly": 0
},
"vpc-331ad056": {
"position": {
"x": -967.090481563079,
"y": 1291.3678057051704
"x": -1435.3398750652455,
"y": 1386.2361491698744
},
"lx": -134.22057243218103,
"ly": 551.6318031828098
},
"vpc-afe722ca": {
"position": {
"x": 593.7116051419532,
"y": 2085.060815603146
"x": -541.0511214212669,
"y": 3010.339213373424
},
"lx": 0,
"ly": 0
},
"vpc-03f799f47b766798c": {
"position": {
"x": -326.8836182123258,
"y": 2966.579150844579
"x": 2417.1718307806645,
"y": 1690.0755318155339
},
"lx": 0,
"ly": 0
},
"subnet-260ee27f": {
"position": {
"x": -921.340481563079,
"y": 1121.1067694739904
"x": -1389.5898750652455,
"y": 1215.9751129386946
},
"lx": 0,
"ly": 0
},
"subnet-f73afeae": {
"position": {
"x": -963.9358574966905,
"y": 1846.410916593678
"x": -1432.185250998857,
"y": 1941.279260058381
},
"lx": 0,
"ly": 0
},
"subnet-ebc4188e": {
"position": {
"x": 642.7195008673785,
"y": 1787.9702751209763
"x": -492.04322569584144,
"y": 2713.2486728912545
},
"lx": 0,
"ly": 0
},
"subnet-005bfd58e719e031f": {
"position": {
"x": 954.3699608755894,
"y": 767.1904595742493
"x": 2795.865315418019,
"y": 521.623811391727
},
"lx": 0,
"ly": 0
},
"subnet-035f81142068bf961": {
"position": {
"x": -727.9701282543923,
"y": 2945.1772029052686
"x": 2015.8700930454238,
"y": 1673.9235838762236
},
"lx": 0,
"ly": 0
},
"subnet-d137fd88": {
"position": {
"x": 1035.1966337030872,
"y": 1260.4186551195244
"x": 2876.691988245517,
"y": 1014.8520069370023
},
"lx": -95.54884268669878,
"ly": -183.7839139599664
},
"subnet-e98e088c": {
"position": {
"x": -556.0376711165936,
"y": 1846.2285577580044
"x": -1024.2870646187603,
"y": 1941.0969012227074
},
"lx": 0,
"ly": 0
},
"subnet-08e40dcdc103438b8": {
"position": {
"x": 549.0070924119668,
"y": 1405.912770994567
"x": 2390.5024469543973,
"y": 1160.3461228120448
},
"lx": 0,
"ly": 0
},
"subnet-0cb2aa7457413f5fe": {
"position": {
"x": -806.65058364074,
"y": 3191.268048237053
"x": 1937.1896376590757,
"y": 1920.0144292080076
},
"lx": 0,
"ly": 0
},
"subnet-247a9341": {
"position": {
"x": -675.3228356289688,
"y": 122.98852970650637
"x": -1205.4162560440936,
"y": 178.27669594691727
},
"lx": 0,
"ly": 0
},
"subnet-34173f72": {
"position": {
"x": -1080.405541820213,
"y": 323.3831217211862
"x": -1548.6549353223795,
"y": 418.2514651858903
},
"lx": 0,
"ly": 0
},
"subnet-05b5dcdc91a8d3da2": {
"position": {
"x": 550.3010345611413,
"y": 1208.7638869426428
"x": 2391.7963891035715,
"y": 963.1972387601206
},
"lx": -56.280967047329966,
"ly": 12.354358620145604
},
"subnet-16594550": {
"position": {
"x": 638.2037094165278,
"y": 2341.8441019824586
"x": -496.5590171466923,
"y": 3267.122499752737
},
"lx": 0,
"ly": 0
},
"subnet-051f6d705e415834d": {
"position": {
"x": 108.34330910685321,
"y": 2965.3802332565056
"x": 2852.1835304066703,
"y": 1694.1266142274605
},
"lx": 0,
"ly": 0
},
"sg-642cf701": {
"position": {
"x": 1368.280952213551,
"y": 1732.122796782028
"x": -128.87428508740095,
"y": 3207.5801320931114
},
"lx": 0,
"ly": 0
@ -170,8 +186,8 @@
},
"sg-da75adbf": {
"position": {
"x": 1654.4153990786074,
"y": 1502.4913097934757
"x": 271.174147224467,
"y": 2826.0633311754773
},
"lx": 0,
"ly": 0
@ -202,224 +218,224 @@
},
"sg-0f2465c4421c2d5c2": {
"position": {
"x": 1501.4220287756627,
"y": 1088.2588218739315
"x": 3737.1319279567347,
"y": 1416.6180986742713
},
"lx": 0,
"ly": 0
},
"sg-8b746aec": {
"position": {
"x": 11.800399713295008,
"y": 754.3055340378875
"x": -57.46491042921801,
"y": 798.8332334152173
},
"lx": -46.67202145388339,
"ly": 75.49885823422312
},
"sg-0a872ac3a6d9132a6": {
"position": {
"x": -273.8173000765115,
"y": 2531.372427285566
"x": 2449.2004574581915,
"y": 2220.1301602993412
},
"lx": 0,
"ly": 0
},
"sg-076d2ea1df0054074": {
"position": {
"x": 297.4040219907455,
"y": 2392.000439156539
"x": 3020.7762653398454,
"y": 2403.6912205594213
},
"lx": 0,
"ly": 0
},
"sg-b45a2bd1": {
"position": {
"x": 1496.2207074206092,
"y": 700.5423557214449
"x": 3732.2813094098105,
"y": 823.5446930393971
},
"lx": 0,
"ly": 0
},
"sg-4235ee27": {
"position": {
"x": 1642.8599868444235,
"y": 2133.250105847895
"x": 134.85579854853708,
"y": 3695.499049118454
},
"lx": 0,
"ly": 0
},
"sg-04167d2e18ba15e45": {
"position": {
"x": -660.0058544043945,
"y": 2397.5181639300217
"x": 2115.6486154236227,
"y": 2405.8871924989144
},
"lx": 0,
"ly": 0
},
"sg-06348763": {
"position": {
"x": 23.18530161221225,
"y": 976.1080421357864
"x": -770.8920039501696,
"y": 953.8441924471215
},
"lx": 0,
"ly": 0
},
"sg-08fb4b412f90d5913": {
"position": {
"x": 57.433554297476746,
"y": 1529.3600510391416
"x": -729.2224680353501,
"y": 1385.8819086010787
},
"lx": 0,
"ly": 0
},
"sg-09621eafa81d9554d": {
"position": {
"x": 1492.9199816248513,
"y": 472.53158920519866
"x": 3699.811020652474,
"y": 413.5355291862605
},
"lx": 0,
"ly": 0
},
"i-0dc281f8d162602c8": {
"position": {
"x": -1201.9240820025686,
"y": 1170.4054563966229
"x": -1670.173475504735,
"y": 1265.2737998613272
},
"lx": 0,
"ly": 0
},
"i-0997a73b08f6e5862": {
"position": {
"x": 696.9537094165278,
"y": 2289.286847879602
"x": -437.8090171466923,
"y": 3214.5652456498806
},
"lx": 0,
"ly": 0
},
"i-0c8100e3460fa8fd0": {
"position": {
"x": -530.5097131980381,
"y": 1487.4564852956444
"x": -998.7591067002047,
"y": 1582.324828760348
},
"lx": 0,
"ly": 0
},
"i-041021cd89e15282c": {
"position": {
"x": 702.4695008673785,
"y": 1812.2202751209763
"x": -432.29322569584144,
"y": 2737.4986728912545
},
"lx": 0,
"ly": 0
},
"i-073b97cbda2b200c3": {
"position": {
"x": 1032.1453149375961,
"y": 1118.630957918435
"x": 2873.640669480026,
"y": 873.0643097359127
},
"lx": 0,
"ly": 0
},
"i-0669f35ab2d0fc444": {
"position": {
"x": 1029.7295075033508,
"y": 1292.025634730957
"x": 2871.2248620457804,
"y": 1046.458986548435
},
"lx": 0,
"ly": 0
},
"i-01b3f3cd57976bdf3": {
"position": {
"x": 1157.7479524685782,
"y": 1450.706352320614
"x": 2999.2433070110083,
"y": 1205.139704138092
},
"lx": 0,
"ly": 0
},
"i-0272763b46610ac1b": {
"position": {
"x": -1204.2644686266099,
"y": 853.4607536515473
"x": -1672.5138621287765,
"y": 948.3290971162517
},
"lx": 238.46757499625102,
"ly": -50.2036999992107
},
"i-0c82adf476c7c5e32": {
"position": {
"x": -1193.7978959064258,
"y": 1010.1880963959779
"x": -1662.0472894085924,
"y": 1105.0564398606823
},
"lx": 0,
"ly": 0
},
"i-0636fd0b033c9b32a": {
"position": {
"x": -515.2568811235894,
"y": 1298.8022809002794
"x": -983.5062746257561,
"y": 1393.6706243649835
},
"lx": 0,
"ly": 0
},
"i-09241599c2590b66a": {
"position": {
"x": -555.6056787872835,
"y": 886.9481918477713
"x": -1023.8550722894502,
"y": 981.8165353124757
},
"lx": 0,
"ly": 0
},
"i-0d7f643cb9d960645": {
"position": {
"x": 57.30327099761796,
"y": 2832.8902534521053
"x": 2801.1434922974354,
"y": 1561.6366344230603
},
"lx": 0,
"ly": 0
},
"i-082b27477bbe6d8b5": {
"position": {
"x": -666.2201282543923,
"y": 2969.4272029052686
"x": 2077.620093045424,
"y": 1698.1735838762236
},
"lx": 0,
"ly": 0
},
"f8-db-01": {
"position": {
"x": -518.2876711165936,
"y": 1864.4785577580044
"x": -986.5370646187603,
"y": 1959.3469012227074
},
"lx": 0,
"ly": 0
},
"logon-db-02": {
"position": {
"x": 694.8906262727602,
"y": 2442.9013560853155
"x": -439.8721002904598,
"y": 3368.179753855594
},
"lx": 0,
"ly": 0
},
"vault-db-production-v2": {
"position": {
"x": 991.0522677808019,
"y": 699.176929169678
"x": 2832.5476223232313,
"y": 453.61028098715565
},
"lx": 0,
"ly": 0
},
"vault-db-staging-v2": {
"position": {
"x": 989.1876539703769,
"y": 871.7039899788206
"x": 2830.6830085128063,
"y": 626.1373417962984
},
"lx": 0,
"ly": 0
},
"NodeAppALB": {
"position": {
"x": 282.38334721608845,
"y": 3145.8702130609063
"x": 3026.2235685159053,
"y": 1874.6165940318608
},
"lx": 0,
"ly": 0