Page 1 of 1

Gargoyle API Access (rpcd and uhttpd-mod-ubus)

Posted: Tue Sep 12, 2023 8:21 am
by Lantis
Install required software
You will need to install rpcd and uhttpd-mod-ubus

Code: Select all

gpkg update
gpkg install rpcd
gpkg install uhttpd-mod-ubus
Check /etc/config/uhttpd has been modified and option ubus_prefix has been set automatically, otherwise

Code: Select all

option ubus_prefix '/ubus'
ubus Calls
Available ubus calls are found by "ubus list"

Code: Select all

root@Gargoyle:~# ubus list
dhcp
dnsmasq
dnsmasq.dns
hotplug.block
hotplug.dhcp
hotplug.iface
hotplug.neigh
hotplug.net
hotplug.ntp
hotplug.openvpn
hotplug.tftp
hotplug.tty
hotplug.usb
hotplug.usbmisc
log
network
network.device
network.interface
network.interface.lan
network.interface.loopback
network.interface.wan
network.interface.wan6
network.interface.wg0
network.wireless
rc
service
session
system
uci
usteer
Methods against each one are found by "-v"

Code: Select all

root@Gargoyle:~# ubus -v list network.interface.wan
'network.interface.wan' @78a6a62b
        "up":{}
        "down":{}
        "renew":{}
        "status":{}
        "prepare":{}
        "dump":{}
        "add_device":{"name":"String","link-ext":"Boolean","vlan":"Array"}
        "remove_device":{"name":"String","link-ext":"Boolean","vlan":"Array"}
        "notify_proto":{}
        "remove":{}
        "set_data":{}
Example "ubus call"

Code: Select all

root@Gargoyle:~# ubus call network.interface.wan status
{
        "up": true,
        "pending": false,
        "available": true,
        "autostart": true,
        "dynamic": false,
        "uptime": 3470914,
        "l3_device": "eth0",
        "proto": "dhcp",
        "device": "eth0",
        "updated": [
                "data"
        ],
        "metric": 0,
        "dns_metric": 0,
        "delegation": true,
        "ipv4-address": [
                {
                        "address": "xxx.xxx.xxx.xxx",
                        "mask": 16
                }
        ],
        "ipv6-address": [

        ],
        "ipv6-prefix": [

        ],
        "ipv6-prefix-assignment": [

        ],
        "route": [
                {
                        "target": "0.0.0.0",
                        "mask": 0,
                        "nexthop": "xxx.xxx.xxx.xxx",
                        "source": "xxx.xxx.xxx.xxx/32"
                }
        ],
        "dns-server": [
                "1.1.1.1",
                "1.0.0.1"
        ],
        "dns-search": [

        ],
        "neighbors": [

        ],
        "inactive": {
                "ipv4-address": [

                ],
                "ipv6-address": [

                ],
                "route": [

                ],
                "dns-server": [
                        "xxx.xxx.xxx.xxx",
                        "xxx.xxx.xxx.xxy"
                ],
                "dns-search": [
                        "ns.xxx.xxx.xxx"
                ],
                "neighbors": [

                ]
        },
        "data": {
                "dhcpserver": "xxx.xxx.xxx.xxx",
                "hostname": "Gargoyle",
                "leaseacquired": 3444277,
                "leasetime": 86400
        }
}
Access control lists (ACLs)
Are defined in /usr/share/rpcd/acl.d/
By default one exists, unauthenticated.json, which allows unauthenticated users to access the "session access" and "session login" methods

Let's login
WARNING: Examples are sent to HTTP, not HTTPS. You will be sending your password IN PLAINTEXT. Consider using HTTPS.

Code: Select all

curl --request POST \
  --url http://192.168.1.1/ubus \
  --header 'Content-Type: application/json' \
  --data '{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "call",
	"params": [
		"00000000000000000000000000000000",
		"session",
		"login",
		{
			"username": "root",
			"password": "YOUR ROOT PASSWORD"
		}
	]
}'

# Response
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		0,
		{
			"ubus_rpc_session": "SESSION TOKEN",
			"timeout": 300,
			"expires": 300,
			"acls": {
				"access-group": {
					"unauthenticated": [
						"read"
					]
				},
				"ubus": {
					"session": [
						"access",
						"login"
					]
				}
			},
			"data": {
				"username": "root"
			}
		}
	]
}
In the response we see several important things
1. SESSION TOKEN. You will need to pass this to any further API requests that are not accessible without first being authenticated
2. EXPIRES. You will need to refresh your token after this period
3. ACLs. These are what you currently have access to. By default, not much.

Let's define an ACL
For accessing the "network.interface.wan status" command

Code: Select all

cp /usr/share/rpcd/acl.d/unauthenticated.json /usr/share/rpcd/acl.d/waninterface.json
vim /usr/share/rpcd/acl.d/waninterface.json
# Copy contents below
{
        "waninterface": {
                "description": "Access controls for WAN Interface",
                "read": {
                        "ubus": {
                                "network.interface.wan": ["status"]
                        }
                }
        }
}
# Copy contents above
Get a new Session Token and check that the system now recognises our new ACLs

Code: Select all

curl --request POST \
  --url http://192.168.1.1/ubus \
  --header 'Content-Type: application/json' \
  --data '{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "call",
	"params": [
		"00000000000000000000000000000000",
		"session",
		"login",
		{
			"username": "root",
			"password": "YOUR ROOT PASSWORD"
		}
	]
}'

# Response
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		0,
		{
			"ubus_rpc_session": "SESSION TOKEN",
			"timeout": 300,
			"expires": 300,
			"acls": {
				"access-group": {
					"unauthenticated": [
						"read"
					],
					"waninterface": [
						"read"
					]
				},
				"ubus": {
					"network.interface.wan": [
						"status"
					],
					"session": [
						"access",
						"login"
					]
				}
			},
			"data": {
				"username": "root"
			}
		}
	]
}
First Useful API Call
Let's call the "network.interface.wan status" API that we now have access to
WARNING: Examples are sent to HTTP, not HTTPS. You will be sending your session token IN PLAINTEXT. Consider using HTTPS.

Code: Select all

curl --request POST \
  --url http://192.168.1.1/ubus \
  --header 'Content-Type: application/json' \
  --data '{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "call",
	"params": [
		"YOUR SESSION TOKEN",
		"network.interface.wan",
		"status",
		{}
	]
}'

# Response
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		0,
		{
			"up": true,
			"pending": false,
			"available": true,
			"autostart": true,
			"dynamic": false,
			"uptime": 3471506,
			"l3_device": "eth0",
			"proto": "dhcp",
			"device": "eth0",
			"updated": [
				"data"
			],
			"metric": 0,
			"dns_metric": 0,
			"delegation": true,
			"ipv4-address": [
				{
					"address": "xxx.xxx.xxx.xxx",
					"mask": 16
				}
			],
			"ipv6-address": [],
			"ipv6-prefix": [],
			"ipv6-prefix-assignment": [],
			"route": [
				{
					"target": "0.0.0.0",
					"mask": 0,
					"nexthop": "xxx.xxx.xxx.xxx",
					"source": "xxx.xxx.xxx.xxx/32"
				}
			],
			"dns-server": [
				"1.1.1.1",
				"1.0.0.1"
			],
			"dns-search": [],
			"neighbors": [],
			"inactive": {
				"ipv4-address": [],
				"ipv6-address": [],
				"route": [],
				"dns-server": [
					"xxx.xxx.xxx.xxx",
					"xxx.xxx.xxx.xxx"
				],
				"dns-search": [
					"ns.xxx.xxx.xxx"
				],
				"neighbors": []
			},
			"data": {
				"dhcpserver": "xxx.xxx.xxx.xxx",
				"hostname": "Gargoyle",
				"leaseacquired": 3444277,
				"leasetime": 86400
			}
		}
	]
}
One more example
Let's define an ACL for accessing the "system board" command

Code: Select all

cp /usr/share/rpcd/acl.d/unauthenticated.json /usr/share/rpcd/acl.d/systemboard.json
vim /usr/share/rpcd/acl.d/systemboard.json
# Copy contents below
{
        "systemboard": {
                "description": "Access controls for system board command",
                "read": {
                        "ubus": {
                                "system": ["board"]
                        }
                }
        }
}
# Copy contents above
Get a new Session Token and check that the system now recognises our new ACLs

Code: Select all

curl --request POST \
  --url http://192.168.1.1/ubus \
  --header 'Content-Type: application/json' \
  --data '{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "call",
	"params": [
		"00000000000000000000000000000000",
		"session",
		"login",
		{
			"username": "root",
			"password": "YOUR ROOT PASSWORD"
		}
	]
}'

# Response
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		0,
		{
			"ubus_rpc_session": "SESSION TOKEN",
			"timeout": 300,
			"expires": 300,
			"acls": {
				"access-group": {
					"systemboard": [
						"read"
					],
					"unauthenticated": [
						"read"
					],
					"waninterface": [
						"read"
					]
				},
				"ubus": {
					"network.interface.wan": [
						"status"
					],
					"session": [
						"access",
						"login"
					],
					"system": [
						"board"
					]
				}
			},
			"data": {
				"username": "root"
			}
		}
	]
}
Let's call the "system board" API that we now have access to
WARNING: Examples are sent to HTTP, not HTTPS. You will be sending your session token IN PLAINTEXT. Consider using HTTPS.

Code: Select all

curl --request POST \
  --url http://192.168.1.1/ubus \
  --header 'Content-Type: application/json' \
  --data '{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "call",
	"params": [
		"YOUR SESSION TOKEN",
		"system",
		"board",
		{}
	]
}'

# Response
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		0,
		{
			"kernel": "5.10.176",
			"hostname": "Gargoyle",
			"system": "Intel(R) Celeron(R) J4125 CPU @ 2.00GHz",
			"model": "Default string Default string",
			"board_name": "default-string-default-string",
			"rootfs_type": "ext4",
			"release": {
				"distribution": "OpenWrt",
				"version": "22.03-SNAPSHOT",
				"revision": "r19160+967-7ea2f3d6e2",
				"target": "x86/64",
				"description": "OpenWrt 22.03-SNAPSHOT r19160+967-7ea2f3d6e2"
			}
		}
	]
}
Granular controls
By default, root can access everything.
You can define users in /etc/config/rpcd and give them less access
This user won't be able to call the "network.interface.wan status" API

Code: Select all

config login 'testuser'
	option username 'testuser'
	option password 'xxxxxxxx'
	list read 'systemboard'