Skip to content

Latest commit

 

History

History
120 lines (76 loc) · 6.67 KB

api-notes.md

File metadata and controls

120 lines (76 loc) · 6.67 KB

What is an API

An API is a set of related methods, accessible over Websocket / HTTP and provided by a single C++ class. API's exist on a per-connection basis.

Compiling with hello_api plugin

We will use the hello_api example plugin. To follow along with the examples, you may enable the hello_api example plugin by running the following command and recompiling:

ln -s ../example_plugins/hello_api external_plugins/hello_api

Publicly available API's

Some API's are public API's which are available to clients without login. These API's may be specified as follows in the configuration file:

public-api = database_api login_api hello_api_api

Note, hello_api is the name of the plugin and hello_api_api is the name of the API it provides. This slightly confusing naming may be revised in a later version of the plugin.

You may also specify --public-api on the command line.

Server configuration note: If you customize public-api configuration, you should set the first two API's to database_api and login_api.

For experts: The reason for recommending database_api and login_api to be the first two API's is that many clients expect these to be first two API's. Additionally, the string API identifier feature's FC implementation requires a working get_api_by_name method to be available on API ID 1, so effectively any client that uses string API identifiers (as recommended) requires login_api to be on API ID 1 even if no client uses the login feature.

Numeric API identifiers

API's are assigned numeric ID's in the order they are specified as public API's. So for example you can access hello_api_api over HTTP like this:

curl --data '{"jsonrpc": "2.0", "params": [2, "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc

The number 2 is the numeric API identifier for hello_api_api. These identifiers are assigned sequentially starting with 0 when the API is registered to the connection. So effectively this means numeric API identifiers are determined by the order of hello_api_api in the public-api declaration above.

The get_api_by_name method on API 1 (the login_api) can be used to query the numeric ID by name:

curl --data '{"jsonrpc": "2.0", "params": [1, "get_api_by_name", ["hello_api_api"]], "id":1, "method":"call"}' http://127.0.0.1:8790/rpc

String API identifiers

Client code that references API's by their numeric identifiers have to coordinated with server-side configuration. This is inconvenient, so the API server supports identifying the API by name as well, like this:

curl --data '{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc

It is considered best practice for API clients to use this syntax to reference API's by their string identifiers. The reason is that the client becomes robust against server-side configuration changes which result in renumbering of API's.

API access control methods

There are three methods to secure the API:

  • Limit access to the API socket to a trusted machine by binding the RPC server to localhost
  • Limit access to the API socket to a trusted LAN by firewall configuration
  • Limit access to particular API's with username/password authentication

The Steem developers recommend using the first of these methods to secure the API by binding to localhost, as follows:

rpc-endpoint = 127.0.0.1:8090

Securing specific API's

The problem with securing API's at the network level is that there are deployment scenarios where a node may want to have some API's public, but other API's private. The steemd process includes username/password based authentication to individual API's.

Since the username/password is sent directly over the wire, you should use a TLS connection when authenticating with username and password. TLS connection can be achieved by one of two methods:

  • Configure an rpc-tls-endpoint.
  • Configure an rpc-endpoint bound to localhost (or a secure LAN), and use an external reverse proxy (for example, nginx). This advanced configuration will not be covered here.

You will need to create a key and certificate for the server in .pem format. Many tutorials for creating SSL certificates exist, and many providers allow creating SSL certificates signed by widely recognized CA's. If you have a working openssl installation you can use these commands to generate a self-signed certificate:

openssl req -x509 -newkey rsa:4096 -keyout data/ss-key.pem -out data/ss-cert.pem -days 365
cat data/ss-{cert,key}.pem > data/ss-cert-key.pem

Then in your config.ini you must give the server access to the key and certificate:

rpc-tls-endpoint = 0.0.0.0:8091
server-pem = data/ss-cert-key.pem
server-pem-password = password

When connecting to the API, you can use the cli_wallet to honor a specific certificate, individual CA, or CA bundle file with the -a option:

programs/cli_wallet/cli_wallet -s wss://mydomain.tld:8091 -a data/ss-cert.pem

Note that the hostname mydomain.tld needs to match the CN field in the certificate.

Now let's protect the hello_api_api API to only be usable by user bytemaster with password supersecret. Modify the config.ini file to make sure we have the correct plugin which provides the sensitive API, make sure the sensitive API is not accessible in public-api, and add an api-user definition to define the access policy:

enable-plugin = witness account_history hello_api
public-api = database_api login_api
api-user = {"username" : "bytemaster", "password_hash_b64" : "T2k/wMBB9BKyv7X+ngghL+MaoubEuFb6GWvF3qQ9NU0=", "password_salt_b64" : "HqK9mAQCkWU=", "allowed_apis" : ["hello_api_api"]}

The values of password_hash_b64 and password_salt_b64 are generated by running programs/util/saltpass.py and entering your desired password.

Username/password access is only available through websockets, since login is inherently stateful. We can use the wscat program to demonstrate the login functionality:

wscat -c wss://mydomain.tld:8091
{"jsonrpc": "2.0", "params": ["database_api", "get_dynamic_global_properties", []], "id":1, "method":"call"}
< (ok)
{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":2, "method":"call"}
< (error)
{"jsonrpc": "2.0", "params": ["login_api", "login", ["bytemaster", "supersecret"]], "id":3, "method":"call"}
< (ok)
{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":4, "method":"call"}
< (ok)

The cli_wallet also has the capability to login to provide access to restricted API's.