From 6b6a9fde2b4d080198f36a414058018919f8bdac Mon Sep 17 00:00:00 2001 From: Randall Naar Date: Fri, 24 May 2024 17:06:34 -0400 Subject: [PATCH] Added code examples to creds.md. --- docs/src/reference/creds.md | 30 +++++++---- examples/python/getting-started/main.py | 33 +++++++----- examples/rust/getting-started/src/main.rs | 61 +++++++++++++++-------- libs/gl-client-py/glclient/glclient.pyi | 4 +- libs/gl-client-py/src/credentials.rs | 23 +++++++-- libs/gl-client-py/tests/fixtures.py | 2 +- libs/gl-testing/gltesting/clients.py | 2 - tools/glcli/glcli/cli.py | 6 +-- 8 files changed, 102 insertions(+), 59 deletions(-) diff --git a/docs/src/reference/creds.md b/docs/src/reference/creds.md index 55ea33738..2db9fe18d 100644 --- a/docs/src/reference/creds.md +++ b/docs/src/reference/creds.md @@ -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" @@ -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 @@ -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 \ No newline at end of file diff --git a/examples/python/getting-started/main.py b/examples/python/getting-started/main.py index 85cf304f2..75a20901e 100644 --- a/examples/python/getting-started/main.py +++ b/examples/python/getting-started/main.py @@ -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) @@ -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] @@ -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] @@ -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, diff --git a/examples/rust/getting-started/src/main.rs b/examples/rust/getting-started/src/main.rs index 6fbeea081..8c59a12b6 100644 --- a/examples/rust/getting-started/src/main.rs +++ b/examples/rust/getting-started/src/main.rs @@ -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}; @@ -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, + signer: &Signer, + device_cert: Vec, + device_key: Vec, +) -> Result { + 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) { fs::write(file_name, data).unwrap(); } +#[allow(unused)] fn read_file(file_name: &str) -> Vec { fs::read(file_name).unwrap() } +#[allow(unused)] async fn create_seed() -> Vec { // ---8<--- [start: create_seed] let mut rng = rand::thread_rng(); @@ -39,12 +60,13 @@ async fn create_seed() -> Vec { seed.to_vec() } +#[allow(unused)] async fn register_node(seed: Vec, 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() }; @@ -56,14 +78,12 @@ async fn register_node(seed: Vec, 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()); @@ -77,16 +97,14 @@ async fn register_node(seed: Vec, 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] @@ -125,6 +143,7 @@ async fn start_node(device_creds_path: String) { // ---8<--- [end: create_invoice] } +#[allow(unused)] async fn recover_node( nobody_cert: Vec, nobody_key: Vec, @@ -132,20 +151,18 @@ async fn recover_node( // ---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] diff --git a/libs/gl-client-py/glclient/glclient.pyi b/libs/gl-client-py/glclient/glclient.pyi index ba515a3dc..4ff13dbe0 100644 --- a/libs/gl-client-py/glclient/glclient.pyi +++ b/libs/gl-client-py/glclient/glclient.pyi @@ -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: ... diff --git a/libs/gl-client-py/src/credentials.rs b/libs/gl-client-py/src/credentials.rs index 4430ba50d..679e81a2e 100644 --- a/libs/gl-client-py/src/credentials.rs +++ b/libs/gl-client-py/src/credentials.rs @@ -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 } } @@ -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 } } diff --git a/libs/gl-client-py/tests/fixtures.py b/libs/gl-client-py/tests/fixtures.py index 0749b65f5..c8214fd8a 100644 --- a/libs/gl-client-py/tests/fixtures.py +++ b/libs/gl-client-py/tests/fixtures.py @@ -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 diff --git a/libs/gl-testing/gltesting/clients.py b/libs/gl-testing/gltesting/clients.py index c4b18deb1..3552e3ebe 100644 --- a/libs/gl-testing/gltesting/clients.py +++ b/libs/gl-testing/gltesting/clients.py @@ -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 diff --git a/tools/glcli/glcli/cli.py b/tools/glcli/glcli/cli.py index 181cfb1b9..5e8ec04c4 100644 --- a/tools/glcli/glcli/cli.py +++ b/tools/glcli/glcli/cli.py @@ -36,8 +36,7 @@ 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") @@ -45,11 +44,10 @@ def __init__(self) -> "Creds": 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.")