Skip to content

horseinthesky/napi

Repository files navigation

napi

napi is a concept HTTP controller for network devices.

💡 Motivation

So you are a network engineer. You are responsible for a huge multivendor network.

To keep the network secure and easy to manage you want only the networking team to have access to network devices. But related teams (developers/SREs) want to collect data and/or even change network device configuration in some cases. It is probably not a good idea to manage access for all these external users, their credentials, SSH keys, permissions, etc. Tacacs could be a solution but you still have to set it up and manage it. And of course, nobody knows the CLI syntax of network boxes and is not eager to learn it. They just want the data!

The main idea of the project is to show how this situation might be solved by establishing a network controller to provide related teams with a familiar HTTP API. It follows DDD approach to keep thing separated, easy to manage, change and extend.

✨ Features

  • 💪 Easy, secure, and vendor agnostic access your network
  • 🚀 Async execution for improved performance powered by FastAPI
  • 📊 Multicore load balancing via Uvicorn ASGI web server
  • 🐳 Docker ready
  • 🔒 Token based authorization
  • 📚 Comprehensive API reference via Swagger and Redoc
  • 📁 Beautiful documentation via MkDocs with material theme.
  • 🛠️ Configurable via .env files and env variables
  • 💾 Completely stateless

📁 Docs

Run the following command to run mkdocs server:

make docs

Then open http://localhost:9000 for more details..

📈 Usage

⚡️ Requirements

Before running napi you need to setup source of thuth (SoT) - an inventory system. By default it has only Netbox support build in.

You can set it up on your own installation or use a community solution.

Don't forget to change view permissions to get full read access without a token by setting:

EXEMPT_VIEW_PERMISSIONS = ['*']

in the configuration/configuration.py file.

And then run:

docker-compose up -d

After Netbox is started load example DB from examples/db_dump.sql.gz with the following command:

gunzip -c db_dump.sql.gz | docker-compose exec -T postgres sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB'

Now you are ready to try it out.

📚 Clone

To try it out clone the repo:

git clone https://github.com/horseinthesky/napi.git

Next install dependencies (poetry is required):

make setup

🛠️ Configuration

Configuration is managed by pydantic's Settings module.

You must have .env file to setup the following settings:

  • nv_api_url (NV_API_URL env var) - an address of Netbox API
  • tenants (TENANTS env var) - comma separated list of tenants network devices belong to in SoT
  • domains (DOMAINS env var) - comma separated list of domains network devices has their fqnds from
  • endpoints (ENDPOINTS env var) - comma separated list of endpoints to use in the environment

Each setting must be prefixed with the corresponding "environment" value. Both in .env file AND as env variable.

By default napi runs in prod environment. It is controlled by ENV env variable.

Here is an example .env file from the repo:

prod_nb_api_url=http://localhost:8000/api
prod_tenants=production
prod_domains=local
prod_endpoints=portswitcher,macgrabber

🚀 Run

Finally run napi server:

make run

By default it runs on port 8080 with several workers (number calculated via nproc).

Swagger

Before moving to further check out API documentation provided automatically by swagger at localhost:8080/docs.

💻 Endpoints

As proof of concept napi provides a few endpoints:

  • ping - simple availability endpoint to put under your load balancer or k8s health check
  • portswitcher - reconfigures DC fabric leaf switch downlinks (server-faced interfaces)
  • macgrabber - gets switch MAC addresses

Ping

Example:

xh get localhost:8080/ping

Always responds with:

{
  "code": 200,
  "status": "ok"
}

Portswitcher

portswitcher is an example of how you can provide an easy vendor agnostic way for your related teams to get/set a network device configuration (L2 interface is this case).

It works the same way with blackbox and whitebox switches. Example supports Huawei CE (Cloud Engine) switches and Nvidia Mellanox switches.

GET

Example:

xh get localhost:8080/api/portswitcher switch=leaf1 interface=GE1/0/5 --bearer token

It expects input data describing the network device interface:

{
  "switch": "string",
  "interface": "string"
}

And returns an abstracted interface "state" - prod/setup:

{
  "code": 200,
  "status": "ok",
  "result": {
    "switch": "string",
    "interface": "string",
    "state": "prod"
  }
}

These prod/setup states are the combination of interface mode (access/trunk) and VLANS on the interface. Check the DB example to see the details.

POST

Example:

xh post localhost:8080/api/portswitcher switch=leaf1 interface=GE1/0/5 state=prod --bearer token

In the same way POST expects data describing the interface plus the desired "state":

{
  "switch": "string",
  "interface": "string",
  "state": "prod"
}

The response is just the same as for GET:

{
  "code": 0,
  "status": "ok",
  "result": {
    "switch": "string",
    "interface": "string",
    "state": "prod"
  }
}

Macgrabber

The same goes for macgrabber which is an example endpoint that provides an easy vendor agnostic way for your related teams to get/set MAC-addresses from a switch. No difference blackbox or whitebox.

GET

Example:

xh get localhost:8080/api/macgrabber switch=leaf2 --bearer token

It expects input data of switch name and (optional) VLAN number:

{
  "switch": "string",
  "vlan": "string"
}

And returns a list of MAC-address info:

{
  "code": 0,
  "status": "ok",
  "result": {
    "switch": "string",
    "macs": [
      {
        "vlan": "string",
        "mac": "string",
        "interface": "string"
      }
    ]
  }
}

🔎 Internals

napi provides three main building blocks to solve any task you might imagine:

  • token authorization
  • inventory system
  • network drivers

🔒 Authorization

Authorization module uses static tokens but you can easily extend it to support JWT or PASETO.

Hash is stored in auth.yml file to authorize the corresponding user.

groups:
  group1: &group1
    permissions:
      - portswitcher
      - macgrabber

users:
  3c469e9d6c5875d37a43f353d4f88e61fcf812c66eee3457465a40b0da4153e0:
    name: user1
    <<: *group1

Example file includes one user user1 with a token token to demonstrate the structure of user permissions.

You can generate new user tokens with this code snippet:

In [1]: import secrets, hashlib

In [2]: token = secrets.token_hex(16)

In [4]: token
Out[4]: 'bf3ae64635504b9b4ce71a6c7ab2bc8c'

In [5]: hashlib.sha256(token.encode('utf-8')).hexdigest()
Out[5]: '95fabfb1713ea5abb54767b2bea0fe49bbd29faad610df7c58c796408a29e7a5'

And assign permissions as in the example above.

📋 Inventory

Inventory module is designed to work with any SoT (Source of Truth) system.

As an example, it has Device, Interface, Vlans, and SupportsGetDeviceInterface abstractions to make the addition of new systems a breeze:

🔌 Drivers

Drivers module provides drivers to interact with network devices via CLI or NETCONF protocol:

  • CLI driver is powered by Carl Montanari's amazing scrapli library.
  • NETCONF driver is powered by robust asyncssh library with in-house wrapper to make in NETCONF ready.

About

A concept HTTP controller for network devices

Resources

License

Stars

Watchers

Forks

Languages