Docs / Environment discovery
Environment discovery
Every IPAM starts empty, and the address space it is supposed to describe already exists. Discovery is how you close that gap: find what is really deployed, see where it collides, and load it in as a registry that reflects reality rather than intentions.
Why this page exists
A registry nobody trusts is worse than no registry, because people route around it. The fastest way to lose that trust is to start from an empty database and ask a team to type in what they already run: the first thing anyone notices is the thing that is missing, and after that they check the real infrastructure instead.
So discovery comes first. Not as a migration chore, but because "what does the registry say" and "what is actually true" have to start out as the same answer, and stay that way.
The routes in
Where your address space lives now decides how it gets in. Most estates need more than one of these.
| Where it lives now | Route in | Status |
|---|---|---|
| Deployed in AWS or Azure | nxip scan, below | Available |
| On-prem, or anywhere with no cloud API | Declare it, with nxip-cli, Terraform, the API, or the dashboard | Available |
| A spreadsheet | Guided CSV import | Designed, not built |
| On-prem, at a scale worth automating | Discovery agent | Designed, not built |
Getting space into nxip is a different job from managing it afterwards. Once it is in, importing existing resources into Terraform is how you bring what nxip already knows about under Terraform management.
Scanning a cloud account
Read-only, no nxip account needed, and nothing leaves your machine. The scan path makes no outbound request other than to the cloud APIs themselves: it never contacts nxip, and the CLI carries no telemetry. Use it before you have signed up for anything.
What it compares against: the networks it discovers, against each other. It reads your cloud APIs, does the overlap analysis locally, and exits. It is not comparing your cloud against what nxip already holds, and there is no account for it to compare against. Diffing a declared estate against your nxip organization is a separate command, nxip plan -f <manifest.yaml>, which does need a key.
npx nxip-cli scan aws
npx nxip-cli scan aws azure # both, analysed as one estate
npx nxip-cli scan aws --region eu-west-2,us-east-1 # narrow it, the default is every region
npx nxip-cli scan azure --subscription <id> # the default is every subscription
npx nxip-cli scan aws azure --redact # safe to share
npx nxip-cli scan aws azure --fail-on-overlap # exit 1 on a real conflict, for CICredentials come from whatever you already use, so for a person at a terminal there is usually nothing to mint. For CI, or if you would rather give the scan its own scoped identity, both clouds take one below.
| Cloud | Permissions | Credential chain |
|---|---|---|
aws | ec2:DescribeVpcs, ec2:DescribeSubnets, and ec2:DescribeRegions for --all-regions. All read-only | Environment, named profile, SSO, instance role |
azure | Read on Microsoft.Network/virtualNetworks, covered by the built-in Reader role | az login, environment, managed or workload identity |
Both providers scan everything by default: every AWS region, and every Azure subscription the identity can see, which is usually where hub-and-spoke overlaps are hiding. Narrow with --region or --subscription. Scanning part of an estate and reporting no overlaps would be a false clean on the one question a scan exists to answer, so the default is everything.
Giving the scan its own identity
Worth doing for CI, and reasonable anywhere: these credentials sit in an environment variable rather than behind an interactive login, so they should be able to read address space and nothing else.
Azure. The built-in Reader role is sufficient, verified against a live subscription:
az ad sp create-for-rbac --name nxip-scan --role Reader --scopes /subscriptions/<subscription-id>
# The three values it prints are the ones DefaultAzureCredential looks for:
export AZURE_CLIENT_ID=<appId>
export AZURE_TENANT_ID=<tenant>
export AZURE_CLIENT_SECRET=<password>
npx nxip-cli scan azureAWS. The standard credential chain applies, so anything you already use works. In order of preference:
- IAM Identity Center, or an assumed role.
aws sso loginthenAWS_PROFILE=your-sso-profile npx nxip-cli scan aws. Best, because no long-lived secret exists to leak: the credentials expire on their own. - An IAM role, on an instance or via OIDC in CI. Also keyless, and the right answer for a pipeline.
- A named profile holding an access key, in
~/.aws/credentials. Fine for a workstation. Better than the next option because the secret sits in a permission-restricted file rather than in the environment of every process you launch. - Environment variables, if you have no AWS CLI installed. Works, but it is a bearer secret with no MFA sitting in your shell, so scope it tightly and delete the key when finished.
# 1 or 2, nothing stored
AWS_PROFILE=my-sso-profile npx nxip-cli scan aws --all-regions
# 4, no AWS CLI required
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
npx nxip-cli scan aws --all-regionsFor a dedicated read-only identity, this is the whole policy. ec2:DescribeRegions is what --all-regions needs; without it the scan can still run against regions you name explicitly:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeRegions"],
"Resource": "*"
}]
}If the scan reports No Azure credentials found, nothing in the chain resolved: run az login, or export the three variables above in the same shell you run the scan from.
Why scanning clouds together is the point
Each cloud's own IPAM is structurally blind to the others. AWS IPAM does not know your Azure hub VNet exists. Azure does not know about your VPCs. Neither knows anything about the datacentre. So the collision that actually stops a project, the one between environments, is the one no vendor tool can show you.
Pass more than one provider and they are merged before analysis, so the comparison runs across the whole estate:
$ npx nxip-cli scan aws -> no overlapping address space found
$ npx nxip-cli scan azure -> no overlapping address space found
$ npx nxip-cli scan aws azure
Overlapping address space: 1 conflict across 2 networks
10.0.0.0/16 claimed by 2 networks
azure vnet-hub (rg-hub/vnet-hub) uksouth 10.0.0.0/16
aws prod-euw2 (vpc-0aa1) eu-west-2 10.0.0.0/16
65,536 addresses in common at most
These cannot be peered or routed to each other without renumbering one side.Each cloud on its own reports nothing. Together they report the thing you needed to know.
Overlaps that are supposed to be there
Not every overlap is a problem, and a tool that pretends otherwise is useless on a real estate. AWS's own EKS guidance recommends carving pod subnets from 100.64.0.0/10 precisely so they do not consume corporate RFC1918 space, which means a fleet of clusters is meant to reuse the same block in every VPC. On a 25-cluster estate that is 300 deliberate overlaps.
These are recognized by default and reported as a count rather than as conflicts:
100.64.0.0/10, RFC 6598 shared address space198.19.0.0/16, RFC 2544 benchmarking range169.254.0.0/16, RFC 3927 link-local
RFC1918 is deliberately absent from that list. Two networks both claiming 10.0.0.0/16 is the exact thing you are scanning for.
Add your own conventions with --exclude 172.20.0.0/14, or see everything with --include-shared. The judgement is made on the overlapping region rather than the network, so a VPC carrying a 100.64 secondary alongside a routable primary still gets its routable collisions reported.
Default VPCs are left alone
AWS creates a default VPC in every region of every account, and it is 172.31.0.0/16 in all of them. Enable 17 regions and you have 17 identical networks nobody deployed, which between them produce 136 pairwise overlaps. On an untouched account that is the entire report, and any real finding is buried underneath it.
So default networks are set aside. They are not compared, not counted as conflicts, and not written into the manifest, and the scan tells you how many it ignored rather than deciding silently:
No overlapping VPC address space found.
Ignored 4 cloud-provisioned default networks (use --include-default-networks to analyse them).This is keyed on the flag, not on the address
The important part: a network is skipped because AWS reports isDefault on it, never because of its CIDR. 172.31.0.0/16 is not a blocklisted range.
If you deliberately built a VPC at 172.31.0.0/16, that is address space you own and are responsible for. It is analysed like any other network, reported if it collides, and imported into your manifest by name. The scan draws the line at boilerplate you did not ask for, not at a block of addresses.
Deleting a default VPC and rebuilding your own in its place is a normal thing to have done, and it is exactly the case a CIDR-based rule would get wrong.
Seeing them anyway
--include-default-networks analyses them like anything else. It is worth running once: if a default VPC has been peered, or has had subnets built in it, it has stopped being boilerplate and belongs in your plan.
The scan output always lists them, marked [default], whether or not they are being analysed. Nothing is hidden from the inventory, only from the conflict analysis.
A default VPC takes its subnets with it, so an emitted manifest lists those separately as left out, saying that they belong to a default network and that nothing is wrong with them. That is deliberately not the same note as the one about a subnet sitting outside every block its network declares, which is a real problem worth investigating.
Azure has no equivalent. It does not create VNets for you, so every VNet a scan finds there is one somebody made, and all of them are analysed.
Sharing a report safely
A report names your accounts, subscriptions, VPCs and VNets. None of that is secret, but together it is an inventory of your estate, and the most useful thing to do with a finding is usually to show it to someone.
--redact replaces every identifier with a stable pseudonym, leaving the finding intact:
nxip scan AWS + AZURE [redacted]
aws aws-account-1 1 region: eu-west-2
azure azure-account-1 1 region: uksouth
Overlapping address space: 1 conflict across 2 networks
10.0.0.0/16 claimed by 2 networks
aws network-1 eu-west-2 10.0.0.0/16
azure network-2 uksouth 10.0.0.0/16It is a transform over the discovered data before analysis runs, not a search-and-replace over the finished text. That distinction matters: scrubbing output afterwards means guessing what an identifier looks like, which both over-matches (a twelve-digit number might be an address count) and under-matches (a VPC named after a customer resembles nothing in particular). Working on the data means the fields are known rather than inferred.
Pseudonyms are stable rather than blanked, because a conflict is the claim that these two networks collide, and replacing both with the same word makes it unreadable.
Address space, regions and every count are kept deliberately. Without them there is no finding left to show, and private ranges identify you weakly at best - a great many organizations use 10.0.0.0/16, whereas an account id identifies exactly one. If your networks carry publicly routable ranges that reasoning does not hold, so read before you post.
Getting it into nxip
Scanning is read-only against the specific CSP(s). Loading what the scan discovered is an additional step.
How a cloud network is modelled
A VPC or VNet is a subnet, not a pool. A pool is the block you carve address space out of. A cloud network is itself carved out of that, so it is a subnet, and the subnets inside it nest underneath it:
| In your estate | In nxip |
|---|---|
Your address plan, say 10.0.0.0/8 | A pool. You create this, once. |
A VPC or VNet, say 10.20.0.0/16 | A subnet in that pool, tagged structurally so things nest under it |
A subnet inside it, say 10.20.1.0/24 | A child subnet of the network above |
So the emitted manifest declares no pools. Only you know what your real address plan is, and a scan will not invent one. Create the pool covering that environment, region and family first; if none exists, nxip plan says so and nothing is created.
Children reference their network by name rather than by id, because nothing in the file exists yet. apply creates each network first and substitutes its real id into the subnets beneath it.
1. Save the manifest
npx nxip-cli scan aws azure --emit-manifest -o aws-azure-discovered.yamlEvery entry uses the CIDR that is actually deployed, so this registers your estate as it really is rather than allocating a parallel plan beside it. Each subnet carries its source network id, account or subscription, and availability zone in metadata, so the link back to the real resource survives the import.
The nesting comes with it. Each network is emitted as a structural subnet and its cloud subnets as children of it, so one apply reproduces the hierarchy you actually have. The pool it lands in is the one thing you provide.
2. Understand the environments it guessed
This is the one part worth reading carefully, because it is where a scan runs out of things it can know.
nxip allows exactly one pool per environment, region and family. Real accounts routinely run several networks in one region, so they cannot all carry the real region name with the same environment - the second would collide. So:
- A region with one network gets
environment: production. - A region with several gets an environment derived from each network's own name, so they stay distinct.
Both are guesses, and the generated file says so at the top. A scan cannot tell which VPC is staging. Change them to match how you actually label things.
3. Review the names too
Names come from AWS Name tags and Azure resource names, which are frequently duplicated, occasionally wrong, and not always what you want nxip to call things. Duplicates are de-duplicated automatically so the file is valid, but the result is mechanical rather than considered.
4. Plan, then apply
nxip plan -f aws-azure-discovered.yaml # dry run, nothing is written
nxip apply -f aws-azure-discovered.yamlplan tells you exactly what would happen before a single record is created. Subnets nesting under a network declared in the same file are listed separately, since their parent does not exist yet and they cannot be previewed independently. They are created after it.
One case is worth knowing about, because a scan runs offline and cannot see your existing pools: if it proposes an environment/region/family combination something else already holds, plan names the conflicting block and tells you to rename it. That is a real finding about the estate rather than a tool problem, and usually means the region already holds an address plan nobody reconciled.
Re-running apply never duplicates anything, so a partial first pass can simply be run again. Pools that already exist are reported as existing and skipped. Subnets are checked against the allocator rather than looked up, so one already registered comes back as an overlap and is counted among the failures: nothing is created twice, but a clean re-run does report those rows rather than staying silent about them.
What discovery does not tell you
Worth being clear about, because the gaps matter as much as the coverage:
- It reads networks and subnets, not what is inside them. It can tell you a
/16is 3% carved. It cannot tell you the carved 3% is itself mostly idle. - IPv6 is listed, not analysed. Cloud providers allocate IPv6 from globally unique space, so the collision problem does not arise the way it does for RFC1918.
- GCP is not supported yet, and it is not simply another module. In GCP only subnets carry CIDR ranges, the network has none and is global rather than regional, and subnets in different regions of one VPC may deliberately share a range.
- On-prem is not scanned at all. There is no API to enumerate a datacentre the way there is for a cloud account, so on-prem space has to be declared rather than discovered. A guided CSV import and a discovery agent are both designed and neither is built yet.
- A scan is a snapshot. It reflects the moment it ran, and says nothing about what changed after.
Keeping it true
The point of loading discovered data is not the import, it is what happens afterwards. A registry that was accurate once and then drifted is back to being a registry nobody trusts.
Two things keep it honest, and both work best once discovery has given you a real starting point:
- Allocate through nxip from then on, with Terraform or nxip-cli, so new space is recorded as it is created rather than reconciled later.
- Re-run
nxip scanperiodically and compare. Anything that appears in the cloud but not in nxip was created around the process, which is exactly what you want to catch early.
Next: the full nxip-cli reference, or how pools and subnets fit together, which is worth reading before you review the environments a scan guessed for you.
