Skip to content

Commit

Permalink
Added code examples to creds.md.
Browse files Browse the repository at this point in the history
  • Loading branch information
Randy808 committed May 28, 2024
1 parent 9e425cb commit 6b6a9fd
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 59 deletions.
30 changes: 19 additions & 11 deletions docs/src/reference/creds.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ Credentials can be reconstructed in various ways and exported in
byte-encoded format for persistence.

!!! tip
If you registered your greenlight node before the release of gl-client v0.2, please see the section [Instantiating a Credential from device certificates] below.
If you registered your greenlight node before the release of gl-client v0.2, please see the section [Instantiating a Credential from device certificates](#instantiating-a-credential-from-device-certificates) below.

## The two variants of the Credential type
## Credential Types
There are two types of Credentials, the Nobody credential and the Device credential.

There are two variants of the Credential type, the Nobody credential and the Device credential.
Reference instantiations of both credentials types can be found below. Complete files can be viewed from the [examples](https://github.com/Blockstream/greenlight/tree/main/examples/rust) folder from which these code snippets were taken.

### Nobody Identity
### Nobody Credentials

The Nobody credential is the credential that's used when there is no registered node associated with your request. It can be initialized using the developer certificates acquired from the [Greenlight Developer Console][gdc] and is used for registering and recovering greenlight nodes.
The Nobody credentials are used when there is no registered node associated with the requests made with the credential. They can be initialized using the developer certificates acquired from the [Greenlight Developer Console][gdc] and are used for registering and recovering greenlight nodes.

[gdc]: https://greenlight.blockstream.com

A reference instantiation taken from the [examples](https://github.com/Blockstream/greenlight/tree/main/examples/rust) can be found below:

=== "Rust"
```rust
--8<-- "main.rs:dev_creds"
Expand All @@ -35,11 +34,9 @@ A reference instantiation taken from the [examples](https://github.com/Blockstre
--8<-- "main.py:dev_creds"
```

### Device Identity

The `Credentials` for a device can be retrieved in numberous ways. They can be restored from a path to a encoded credentials file, as well as from a byte array that carries the same data. `Credentials` for the device can also be constructed by their components or a combination of all of the above.
### Device Credentials

How to build `Credentials` from encoded formats?
The Device credentials are used when there is a registered node associated with the requests made with the credential. They can be restored from a path to a encoded credentials file, as well as from a byte array that carries the same data. `Credentials` for the device can also be constructed by their components or a combination of all of the above.

=== "Rust"
```rust
Expand All @@ -53,6 +50,17 @@ How to build `Credentials` from encoded formats?

## Instantiating a Credential from device certificates

For glclient versions released before v0.2, device certificates were the primary mechanism used for authentication. These certificates can be upgraded by instantiating a Device credential and invoking the upgrade method with a Nobody-instantiated Scheduler and a Signer. This will give the upgrade method everything it needs to construct any missing details and will return a properly functioning Device credential.

=== "Rust"
```rust
--8<-- "main.rs:upgrade_device_certs_to_creds"
```

=== "Python"
```python
--8<-- "main.py:upgrade_device_certs_to_creds"
```


[security]: ./security.md
33 changes: 20 additions & 13 deletions examples/python/getting-started/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import bip39 # type: ignore
from glclient import Credentials, Signer, Scheduler # type: ignore
import bip39 # type: ignore
from glclient import Credentials, Signer, Scheduler # type: ignore
from pathlib import Path
from pyln import grpc as clnpb # type: ignore
from pyln import grpc as clnpb # type: ignore
import secrets # Make sure to use cryptographically sound randomness


# ---8<--- [start: upgrade_device_certs_to_creds]
def upgrade_device_certs_to_creds(
scheduler: Scheduler, signer: Signer, device_cert: bytes, device_key: bytes
):
device_creds = Credentials.from_parts(device_cert, device_key)
return device_creds.upgrade(scheduler, signer)
# ---8<--- [end: upgrade_device_certs_to_creds]


def save_to_file(file_name: str, data: bytes) -> None:
with open(file_name, "wb") as file:
file.write(data)
Expand All @@ -31,16 +40,14 @@ def create_seed() -> bytes:
return seed


def nobody_with_identity(developer_cert: bytes, developer_key: bytes) -> Credentials:
ca = Path("ca.pem").open(mode="rb").read()
return Credentials.nobody_with(developer_cert, developer_key, ca)

def register_node(seed: bytes, developer_cert_path: str, developer_key_path: str) -> None:
def register_node(
seed: bytes, developer_cert_path: str, developer_key_path: str
) -> None:
# ---8<--- [start: dev_creds]
developer_cert = Path(developer_cert_path).open(mode="rb").read()
developer_key = Path(developer_key_path).open(mode="rb").read()

developer_creds = nobody_with_identity(developer_cert, developer_key)
developer_creds = Credentials.nobody_with(developer_cert, developer_key)
# ---8<--- [end: dev_creds]

# ---8<--- [start: init_signer]
Expand All @@ -57,9 +64,9 @@ def register_node(seed: bytes, developer_cert_path: str, developer_key_path: str

# ---8<--- [start: device_creds]
device_creds = Credentials.from_bytes(registration_response.creds)
save_to_file("creds", device_creds.to_bytes());
save_to_file("creds", device_creds.to_bytes())
# ---8<--- [end: device_creds]

# ---8<--- [end: register_node]

# ---8<--- [start: get_node]
Expand Down Expand Up @@ -103,9 +110,9 @@ def recover_node(developer_cert: bytes, developer_key: bytes) -> None:
# ---8<--- [start: recover_node]
seed = read_file("seed")
network = "bitcoin"
signer_creds = nobody_with_identity(developer_cert, developer_key)
signer_creds = Credentials.nobody_with(developer_cert, developer_key)
signer = Signer(seed, network, signer_creds)

scheduler = Scheduler(
network,
signer_creds,
Expand Down
61 changes: 39 additions & 22 deletions examples/rust/getting-started/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::fs::{self};
use std::path::{Path};
use anyhow::{anyhow, Result};
use bip39::{Language, Mnemonic};
use gl_client::credentials::{Device, Nobody};
Expand All @@ -8,19 +6,42 @@ use gl_client::pb::cln::{amount_or_any, Amount, AmountOrAny};
use gl_client::pb::{self, cln};
use gl_client::scheduler::Scheduler;
use gl_client::{bitcoin::Network, signer::Signer};
use std::fs::{self};
use tokio;

#[tokio::main]
async fn main() {}

#[allow(unused)]
// ---8<--- [start: upgrade_device_certs_to_creds]
async fn upgrade_device_certs_to_creds(
scheduler: &Scheduler<Nobody>,
signer: &Signer,
device_cert: Vec<u8>,
device_key: Vec<u8>,
) -> Result<Device> {
Device {
cert: device_cert,
key: device_key,
..Default::default()
}
.upgrade(scheduler, signer)
.await
.map_err(|e| anyhow!("{}", e.to_string()))
}
// ---8<--- [end: upgrade_device_certs_to_creds]

#[allow(unused)]
fn save_to_file(file_name: &str, data: Vec<u8>) {
fs::write(file_name, data).unwrap();
}

#[allow(unused)]
fn read_file(file_name: &str) -> Vec<u8> {
fs::read(file_name).unwrap()
}

#[allow(unused)]
async fn create_seed() -> Vec<u8> {
// ---8<--- [start: create_seed]
let mut rng = rand::thread_rng();
Expand All @@ -39,12 +60,13 @@ async fn create_seed() -> Vec<u8> {
seed.to_vec()
}

#[allow(unused)]
async fn register_node(seed: Vec<u8>, developer_cert_path: String, developer_key_path: String) {
// ---8<--- [start: dev_creds]
let developer_cert = std::fs::read(developer_cert_path).unwrap_or_default();
let developer_key = std::fs::read(developer_key_path).unwrap_or_default();
let developer_creds = Nobody{
cert: developer_cert,
let developer_creds = Nobody {
cert: developer_cert,
key: developer_key,
..Nobody::default()
};
Expand All @@ -56,14 +78,12 @@ async fn register_node(seed: Vec<u8>, developer_cert_path: String, developer_key
// ---8<--- [end: init_signer]

// ---8<--- [start: register_node]
let scheduler = Scheduler::new(network, developer_creds)
.await
.unwrap();
let scheduler = Scheduler::new(network, developer_creds).await.unwrap();

// Passing in the signer is required because the client needs to prove
// ownership of the `node_id`
let registration_response = scheduler.register(&signer, None).await.unwrap();

// ---8<--- [start: device_creds]
let device_creds = Device::from_bytes(registration_response.creds);
save_to_file("creds", device_creds.to_bytes());
Expand All @@ -77,16 +97,14 @@ async fn register_node(seed: Vec<u8>, developer_cert_path: String, developer_key
// ---8<--- [end: get_node]
}

#[allow(unused)]
async fn start_node(device_creds_path: String) {
// ---8<--- [start: start_node]
let network = Network::Bitcoin;
let device_creds = Device::from_path(device_creds_path);
let scheduler = gl_client::scheduler::Scheduler::new(
network,
device_creds.clone(),
)
.await
.unwrap();
let scheduler = gl_client::scheduler::Scheduler::new(network, device_creds.clone())
.await
.unwrap();

let mut node: gl_client::node::ClnClient = scheduler.node().await.unwrap();
// ---8<--- [end: start_node]
Expand Down Expand Up @@ -125,27 +143,26 @@ async fn start_node(device_creds_path: String) {
// ---8<--- [end: create_invoice]
}

#[allow(unused)]
async fn recover_node(
nobody_cert: Vec<u8>,
nobody_key: Vec<u8>,
) -> Result<pb::scheduler::RecoveryResponse> {
// ---8<--- [start: recover_node]
let seed = read_file("seed");
let network = gl_client::bitcoin::Network::Bitcoin;
let creds = Nobody{
let creds = Nobody {
cert: nobody_cert,
key: nobody_key,
..Nobody::default()
};

let signer = gl_client::signer::Signer::new(seed, network, creds.clone()).unwrap();

let scheduler = gl_client::scheduler::Scheduler::new(
gl_client::bitcoin::Network::Bitcoin,
creds,
)
.await
.unwrap();
let scheduler =
gl_client::scheduler::Scheduler::new(gl_client::bitcoin::Network::Bitcoin, creds)
.await
.unwrap();

scheduler.recover(&signer).await
// ---8<--- [end: recover_node]
Expand Down
4 changes: 2 additions & 2 deletions libs/gl-client-py/glclient/glclient.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ class TlsConfig:
class Credentials:
def __init__(self) -> None: ...
@staticmethod
def nobody_with(cert: bytes, key: bytes, ca: bytes) -> Credentials: ...
def nobody_with(cert: bytes, key: bytes, ca: bytes=None) -> Credentials: ...
@staticmethod
def from_bytes(data: bytes) -> Credentials: ...
@staticmethod
def from_path(path: str) -> Credentials: ...
@staticmethod
def from_parts(cert: bytes, key: bytes, ca: bytes, rune: str) -> Credentials: ...
def from_parts(cert: bytes, key: bytes, rune: str, ca: bytes=None) -> Credentials: ...
def node_id(self) -> bytes: ...
def upgrade(self, scheduler: Scheduler, signer: Signer) -> Credentials: ...
def to_bytes(self) -> bytes: ...
Expand Down
23 changes: 19 additions & 4 deletions libs/gl-client-py/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,18 @@ impl Credentials {
}

#[staticmethod]
pub fn nobody_with(cert: &[u8], key: &[u8], ca: &[u8]) -> Self {
let inner = UnifiedCredentials::Nobody(gl_client::credentials::Nobody::with(cert, key, ca));
pub fn nobody_with(cert: &[u8], key: &[u8], ca: Option<&[u8]>) -> Self {
let ca = ca.map_or_else(
|| credentials::Nobody::default().ca,
Into::into
);

let inner = UnifiedCredentials::Nobody(gl_client::credentials::Nobody::with(
cert,
key,
&ca
));

log::debug!("Created NOBODY credentials");
Self { inner }
}
Expand All @@ -124,9 +134,14 @@ impl Credentials {
}

#[staticmethod]
pub fn from_parts(cert: &[u8], key: &[u8], ca: &[u8], rune: &str) -> Self {
pub fn from_parts(cert: &[u8], key: &[u8], rune: &str, ca: Option<&[u8]>) -> Self {
let ca = ca.map_or_else(
|| credentials::Nobody::default().ca,
Into::into
);

let inner =
UnifiedCredentials::Device(gl_client::credentials::Device::with(cert, key, ca, rune));
UnifiedCredentials::Device(gl_client::credentials::Device::with(cert, key, &ca, rune));
Self { inner }
}

Expand Down
2 changes: 1 addition & 1 deletion libs/gl-client-py/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def creds(nobody_id):
"""Nobody credentials for the tests."""
creds = Credentials.nobody_with(
nobody_id.cert_chain, nobody_id.private_key, nobody_id.caroot
nobody_id.cert_chain, nobody_id.private_key
)
return creds

Expand Down
2 changes: 0 additions & 2 deletions libs/gl-testing/gltesting/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,10 @@ def creds(self) -> glclient.Credentials:
else:
certpath = self.directory / "nobody.crt"
keypath = self.directory / "nobody-key.pem"
capath = self.directory / "ca.crt"
self.log.info(f"Loading generic nobody credentials from {self.directory}")
creds = glclient.Credentials.nobody_with(
certpath.open(mode="rb").read(),
keypath.open(mode="rb").read(),
capath.open(mode="rb").read(),
)
return creds

Expand Down
6 changes: 2 additions & 4 deletions tools/glcli/glcli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,18 @@ def __init__(self) -> "Creds":
# legacy paths used by TlsConfig
cert_path = Path("device.crt")
key_path = Path("device-key.pem")
ca_path = Path("ca.pem")
have_certs = cert_path.exists() and key_path.exists() and ca_path.exists()
have_certs = cert_path.exists() and key_path.exists()
if creds_path.exists():
self.creds = Credentials.from_path(str(creds_path))
logger.info("Configuring client with device credentials")
elif have_certs:
logger.info("Configuring client with device credentials (legacy)")
device_cert = open(str(cert_path), "rb").read()
device_key = open(str(key_path), "rb").read()
ca = open(str(ca_path), "rb").read()
rune = ""
if Path("rune").exists():
rune = open("rune", "r").read()
self.creds = Credentials.from_parts(device_cert, device_key, ca, rune)
self.creds = Credentials.from_parts(device_cert, device_key, rune)
else:
logger.info("Configuring client with NOBODY credentials.")

Expand Down

0 comments on commit 6b6a9fd

Please sign in to comment.