From a16c4e698aed1383693cfdc321b22150c1583799 Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Sat, 11 Feb 2023 13:26:08 -0600 Subject: [PATCH] feat: gRPC authorization for events closes: https://todo.sr.ht/~gheartsfield/nostr-rs-relay/46 --- Cargo.lock | 81 +++ Cargo.toml | 5 + build.rs | 4 + config.toml | 10 + docs/grpc-extensions.md | 79 +++ nauthz_server_example/Cargo.lock | 1010 +++++++++++++++++++++++++++++ nauthz_server_example/Cargo.toml | 13 + nauthz_server_example/build.rs | 4 + nauthz_server_example/src/main.rs | 61 ++ proto/nauthz.proto | 60 ++ src/config.rs | 10 + src/db.rs | 57 +- src/error.rs | 11 + src/lib.rs | 1 + src/nauthz.rs | 110 ++++ src/nip05.rs | 4 +- src/server.rs | 10 +- 17 files changed, 1522 insertions(+), 8 deletions(-) create mode 100644 build.rs create mode 100644 docs/grpc-extensions.md create mode 100644 nauthz_server_example/Cargo.lock create mode 100644 nauthz_server_example/Cargo.toml create mode 100644 nauthz_server_example/build.rs create mode 100644 nauthz_server_example/src/main.rs create mode 100644 proto/nauthz.proto create mode 100644 src/nauthz.rs diff --git a/Cargo.lock b/Cargo.lock index af8d027..e73aa57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,6 +783,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.25" @@ -1490,6 +1496,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "native-tls" version = "0.2.11" @@ -1556,6 +1568,7 @@ dependencies = [ "nonzero_ext", "parse_duration", "prometheus", + "prost", "r2d2", "r2d2_sqlite", "rand 0.8.5", @@ -1568,6 +1581,8 @@ dependencies = [ "thiserror", "tokio", "tokio-tungstenite", + "tonic", + "tonic-build", "tracing", "tracing-subscriber 0.2.25", "tungstenite", @@ -1861,6 +1876,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -1925,6 +1950,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1983,6 +2018,28 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + [[package]] name = "prost-derive" version = "0.11.6" @@ -2926,6 +2983,19 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + [[package]] name = "tower" version = "0.4.13" @@ -3353,6 +3423,17 @@ dependencies = [ "cc", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "whoami" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 6028f56..87ffcbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ clap = { version = "4.0.32", features = ["env", "default", "derive"]} tracing = "0.1.36" tracing-subscriber = "0.2.0" tokio = { version = "1", features = ["full", "tracing", "signal"] } +prost = "0.11" +tonic = "0.8.3" console-subscriber = "0.1.8" futures = "0.3" futures-util = "0.3" @@ -52,3 +54,6 @@ bech32 = "0.9.1" [dev-dependencies] anyhow = "1" + +[build-dependencies] +tonic-build = { version="0.8.3", features = ["prost"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0a1101c --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/nauthz.proto")?; + Ok(()) +} diff --git a/config.toml b/config.toml index dfdbc80..9fd24cb 100644 --- a/config.toml +++ b/config.toml @@ -48,6 +48,16 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i # sqlite. #connection = "postgresql://postgres:nostr@localhost:7500/nostr" +[grpc] +# gRPC interfaces for externalized decisions and other extensions to +# functionality. +# +# Events can be authorized through an external service, by providing +# the URL below. In the event the server is not accessible, events +# will be permitted. The protobuf3 schema used is available in +# `proto/nauthz.proto`. +# event_authorization_server = "http://[::1]:50051" + [network] # Bind to this network address address = "0.0.0.0" diff --git a/docs/grpc-extensions.md b/docs/grpc-extensions.md new file mode 100644 index 0000000..72f751d --- /dev/null +++ b/docs/grpc-extensions.md @@ -0,0 +1,79 @@ +# gRPC Extensions Design Document + +The relay will be extensible through gRPC endpoints, definable in the +main configuration file. These will allow external programs to host +logic for deciding things such as, should this event be persisted, +should this connection be allowed, and should this subscription +request be registered. The primary goal is allow for relay operator +specific functionality that allows them to serve smaller communities +and reduce spam and abuse. + +This will likely evolve substantially, the first goal is to get a +basic one-way service that lets an externalized program decide on +event persistance. This does not represent the final state of gRPC +extensibility in `nostr-rs-relay`. + +## Considerations + +Write event latency must not be significantly affected. However, the +primary reason we are implementing this is spam/abuse protection, so +we are willing to tolerate some increase in latency if that protects +us against outages! + +The interface should provide enough information to make simple +decisions, without burdening the relay to do extra queries. The +decision endpoint will be mostly responsible for maintaining state and +gathering additional details. + +## Design Overview + +A gRPC server may be defined in the `config.toml` file. If it exists, +the relay will attempt to connect to it and send a message for each +`EVENT` command submitted by clients. If a successful response is +returned indicating the event is permitted, the relay continues +processing the event as normal. All existing whitelist, blacklist, +and `NIP-05` validation checks are still performed and MAY still +result in the event being rejected. If a successful response is +returned indicated the decision is anything other than permit, then +the relay MUST reject the event, and return a command result to the +user (using `NIP-20`) indicating the event was blocked (optionally +providing a message). + +In the event there is an error in the gRPC interface, event processing +proceeds as if gRPC was disabled (fail open). This allows gRPC +servers to be deployed with minimal chance of causing a full relay +outage. + +## Design Details + +Currently one procedure call is supported, `EventAdmit`, in the +`Authorization` service. It accepts the following data in order to +support authorization decisions: + +- The event itself +- The client IP that submitted the event +- The client's HTTP origin header, if one exists +- The client's HTTP user agent header, if one exists +- The public key of the client, if `NIP-42` authentication was + performed (not supported in the relay yet!) +- The `NIP-05` associated with the event's public key, if it is known + to the relay + +A server providing authorization decisions will return the following: + +- A decision to permit or deny the event +- An optional message that explains why the event was denied, to be + transmitted to the client + +## Security Issues + +There is little attempt to secure this interface, since it is intended +for use processes running on the same host. It is recommended to +ensure that the gRPC server providing the API is not exposed to the +public Internet. Authorization server implementations should have +their own security reviews performed. + +A slow gRPC server could cause availability issues for event +processing, since this is performed on a single thread. Avoid any +expensive or long-running processes that could result from submitted +events, since any client can initiate a gRPC call to the service. diff --git a/nauthz_server_example/Cargo.lock b/nauthz_server_example/Cargo.lock new file mode 100644 index 0000000..cc93a59 --- /dev/null +++ b/nauthz_server_example/Cargo.lock @@ -0,0 +1,1010 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nauthz-server" +version = "0.1.0" +dependencies = [ + "prost", + "tokio", + "tonic", + "tonic-build", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/nauthz_server_example/Cargo.toml b/nauthz_server_example/Cargo.toml new file mode 100644 index 0000000..70b6c32 --- /dev/null +++ b/nauthz_server_example/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nauthz-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Common dependencies +tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } +prost = "0.11" +tonic = "0.8.3" + +[build-dependencies] +tonic-build = { version="0.8.3", features = ["prost"] } diff --git a/nauthz_server_example/build.rs b/nauthz_server_example/build.rs new file mode 100644 index 0000000..b5b1f01 --- /dev/null +++ b/nauthz_server_example/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("../proto/nauthz.proto")?; + Ok(()) +} diff --git a/nauthz_server_example/src/main.rs b/nauthz_server_example/src/main.rs new file mode 100644 index 0000000..fb0fbce --- /dev/null +++ b/nauthz_server_example/src/main.rs @@ -0,0 +1,61 @@ +use tonic::{transport::Server, Request, Response, Status}; + +use nauthz_grpc::authorization_server::{Authorization, AuthorizationServer}; +use nauthz_grpc::{EventReply, EventRequest, Decision}; + +pub mod nauthz_grpc { + tonic::include_proto!("nauthz"); +} + +#[derive(Default)] +pub struct EventAuthz { + allowed_kinds: Vec, +} + +#[tonic::async_trait] +impl Authorization for EventAuthz { + + async fn event_admit( + &self, + request: Request, + ) -> Result, Status> { + let reply; + let req = request.into_inner(); + let event = req.event.unwrap(); + let content_prefix:String = event.content.chars().take(40).collect(); + println!("recvd event, [kind={}, origin={:?}, nip05_domain={:?}, tag_count={}, content_sample={:?}]", + event.kind, req.origin, req.nip05.map(|x| x.domain), event.tags.len(), content_prefix); + // Permit any event with a whitelisted kind + if self.allowed_kinds.contains(&event.kind) { + println!("This looks fine! (kind={})",event.kind); + reply = nauthz_grpc::EventReply { + decision: Decision::Permit as i32, + message: None + }; + } else { + println!("Blocked! (kind={})",event.kind); + reply = nauthz_grpc::EventReply { + decision: Decision::Deny as i32, + message: Some(format!("kind {} not permitted", event.kind)), + }; + } + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse().unwrap(); + + // A simple authorization engine that allows kinds 1, 2, and 3. + let checker = EventAuthz { + allowed_kinds: vec![0,1,2,3], + }; + println!("EventAuthz Server listening on {}", addr); + // Start serving + Server::builder() + .add_service(AuthorizationServer::new(checker)) + .serve(addr) + .await?; + Ok(()) +} diff --git a/proto/nauthz.proto b/proto/nauthz.proto new file mode 100644 index 0000000..829b273 --- /dev/null +++ b/proto/nauthz.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +// Nostr Authorization Services +package nauthz; + +// Authorization for actions against a relay +service Authorization { + // Determine if an event should be admitted to the relay + rpc EventAdmit(EventRequest) returns (EventReply) {} +} + +message Event { + bytes id = 1; // 32-byte SHA256 hash of serialized event + bytes pubkey = 2; // 32-byte public key of event creator + fixed64 created_at = 3; // UNIX timestamp provided by event creator + uint64 kind = 4; // event kind + string content = 5; // arbitrary event contents + repeated TagEntry tags = 6; // event tag array + bytes sig = 7; // 32-byte signature of the event id + // Individual values for a single tag + message TagEntry { + repeated string values = 1; + } +} + +// Event data and metadata for authorization decisions +message EventRequest { + Event event = + 1; // the event to be admitted for further relay processing + optional string ip_addr = + 2; // IP address of the client that submitted the event + optional string origin = + 3; // HTTP origin header from the client, if one exists + optional string user_agent = + 4; // HTTP user-agent header from the client, if one exists + optional bytes auth_pubkey = + 5; // the public key associated with a NIP-42 AUTH'd session, if + // authentication occurred + optional Nip05Name nip05 = + 6; // NIP-05 address associated with the event pubkey, if it is + // known and has been validated by the relay + // A NIP_05 verification record + message Nip05Name { + string local = 1; + string domain = 2; + } +} + +// A permit or deny decision +enum Decision { + DECISION_UNSPECIFIED = 0; + DECISION_PERMIT = 1; // Admit this event for further processing + DECISION_DENY = 2; // Deny persisting or propagating this event +} + +// Response to a event authorization request +message EventReply { + Decision decision = 1; // decision to enforce + optional string message = 2; // informative message for the client +} diff --git a/src/config.rs b/src/config.rs index 51f2536..eff1f7b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,12 @@ pub struct Database { pub connection: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[allow(unused)] +pub struct Grpc { + pub event_admission_server: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(unused)] pub struct Network { @@ -145,6 +151,7 @@ pub struct Settings { pub info: Info, pub diagnostics: Diagnostics, pub database: Database, + pub grpc: Grpc, pub network: Network, pub limits: Limits, pub authorization: Authorization, @@ -220,6 +227,9 @@ impl Default for Settings { max_conn: 8, connection: "".to_owned(), }, + grpc: Grpc { + event_admission_server: None, + }, network: Network { port: 8080, ping_interval_seconds: 300, diff --git a/src/db.rs b/src/db.rs index 516bff3..ce560e7 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,6 +4,7 @@ use crate::error::{Error, Result}; use crate::event::Event; use crate::notice::Notice; use crate::server::NostrMetrics; +use crate::nauthz; use governor::clock::Clock; use governor::{Quota, RateLimiter}; use r2d2; @@ -27,6 +28,8 @@ pub struct SubmittedEvent { pub event: Event, pub notice_tx: tokio::sync::mpsc::Sender, pub source_ip: String, + pub origin: Option, + pub user_agent: Option, } /// Database file @@ -101,6 +104,18 @@ pub async fn db_writer( lim_opt = Some(RateLimiter::direct(Quota::per_minute(quota))); } } + // create a client if GRPC is enabled. + // Check with externalized event admitter service, if one is defined. + let mut grpc_client = if let Some(svr) = settings.grpc.event_admission_server { + Some(nauthz::EventAuthzService::connect(&svr).await) + } else { + None + }; + + //let gprc_client = settings.grpc.event_admission_server.map(|s| { +// event_admitter_connect(&s); +// }); + loop { if shutdown.try_recv().is_ok() { info!("shutting down database writer"); @@ -165,9 +180,17 @@ pub async fn db_writer( metadata_tx.send(event.clone()).ok(); } + // get a validation result for use in verification and GPRC + let validation = if nip05_active { + Some(repo.get_latest_user_verification(&event.pubkey).await) + } else { + None + }; + + // check for NIP-05 verification - if nip05_enabled { - match repo.get_latest_user_verification(&event.pubkey).await { + if nip05_enabled && validation.is_some() { + match validation.as_ref().unwrap() { Ok(uv) => { if uv.is_valid(&settings.verified_users) { info!( @@ -175,6 +198,7 @@ pub async fn db_writer( uv.name.to_string(), event.get_author_prefix() ); + } else { info!( "rejecting event, author ({:?} / {:?}) verification invalid (expired/wrong domain)", @@ -209,6 +233,35 @@ pub async fn db_writer( } } } + + // nip05 address + let nip05_address : Option = validation.and_then(|x| x.ok().map(|y| y.name)); + + // GRPC check + if let Some(ref mut c) = grpc_client { + trace!("checking if grpc permits"); + let grpc_start = Instant::now(); + let decision_res = c.admit_event(&event, &subm_event.source_ip, subm_event.origin, subm_event.user_agent, nip05_address).await; + match decision_res { + Ok(decision) => { + if !decision.permitted() { + // GPRC returned a decision to reject this event + info!("GRPC rejected event: {:?} (kind: {}) from: {:?} in: {:?} (IP: {:?})", + event.get_event_id_prefix(), + event.kind, + event.get_author_prefix(), + grpc_start.elapsed(), + subm_event.source_ip); + notice_tx.try_send(Notice::blocked(event.id, &decision.message().unwrap_or_else(|| "".to_string()))).ok(); + continue; + } + }, + Err(e) => { + warn!("GRPC server error: {:?}", e); + } + } + } + // TODO: cache recent list of authors to remove a DB call. let start = Instant::now(); if event.is_ephemeral() { diff --git a/src/error.rs b/src/error.rs index f931b92..5114fb5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -64,6 +64,10 @@ pub enum Error { DelegationParseError, #[error("Channel closed error")] ChannelClosed, + #[error("Authz error")] + AuthzError, + #[error("Tonic GRPC error")] + TonicError(tonic::Status), #[error("Unknown/Undocumented")] UnknownError, } @@ -132,3 +136,10 @@ impl From for Error { Error::ConfigError(r) } } + +impl From for Error { + /// Wrap Config error + fn from(r: tonic::Status) -> Self { + Error::TonicError(r) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7b13866..4df90e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod event; pub mod hexrange; pub mod info; pub mod nip05; +pub mod nauthz; pub mod notice; pub mod repo; pub mod subscription; diff --git a/src/nauthz.rs b/src/nauthz.rs new file mode 100644 index 0000000..b79ed3e --- /dev/null +++ b/src/nauthz.rs @@ -0,0 +1,110 @@ +use crate::error::{Error, Result}; +use crate::{event::Event, nip05::Nip05Name}; +use nauthz_grpc::authorization_client::AuthorizationClient; +use nauthz_grpc::event::TagEntry; +use nauthz_grpc::{Decision, Event as GrpcEvent, EventReply, EventRequest}; +use tracing::{info, warn}; + +pub mod nauthz_grpc { + tonic::include_proto!("nauthz"); +} + +// A decision for the DB to act upon +pub trait AuthzDecision: Send + Sync { + fn permitted(&self) -> bool; + fn message(&self) -> Option; +} + +impl AuthzDecision for EventReply { + fn permitted(&self) -> bool { + self.decision == Decision::Permit as i32 + } + fn message(&self) -> Option { + self.message.clone() + } +} + +// A connection to an event admission GRPC server +pub struct EventAuthzService { + server_addr: String, + conn: Option>, +} + +// conversion of Nip05Names into GRPC type +impl std::convert::From for nauthz_grpc::event_request::Nip05Name { + fn from(value: Nip05Name) -> Self { + nauthz_grpc::event_request::Nip05Name { + local: value.local.clone(), + domain: value.domain.clone(), + } + } +} + +// conversion of event tags into gprc struct +fn tags_to_protobuf(tags: &Vec>) -> Vec { + tags.iter() + .map(|x| TagEntry { values: x.clone() }) + .collect() +} + +impl EventAuthzService { + pub async fn connect(server_addr: &str) -> EventAuthzService { + let mut eas = EventAuthzService { + server_addr: server_addr.to_string(), + conn: None, + }; + eas.ready_connection().await; + eas + } + + pub async fn ready_connection(self: &mut Self) { + if self.conn.is_none() { + let client = AuthorizationClient::connect(self.server_addr.to_string()).await; + if let Err(ref msg) = client { + warn!("could not connect to nostr authz GRPC server: {:?}", msg); + } else { + info!("connected to nostr authorization GRPC server"); + } + self.conn = client.ok(); + } + } + + pub async fn admit_event( + self: &mut Self, + event: &Event, + ip: &str, + origin: Option, + user_agent: Option, + nip05: Option, + ) -> Result> { + self.ready_connection().await; + let id_blob = hex::decode(&event.id)?; + let pubkey_blob = hex::decode(&event.pubkey)?; + let sig_blob = hex::decode(&event.sig)?; + if let Some(ref mut c) = self.conn { + let gevent = GrpcEvent { + id: id_blob, + pubkey: pubkey_blob, + sig: sig_blob, + created_at: event.created_at, + kind: event.kind, + content: event.content.clone(), + tags: tags_to_protobuf(&event.tags), + }; + let svr_res = c + .event_admit(EventRequest { + event: Some(gevent), + ip_addr: Some(ip.to_string()), + origin, + user_agent, + auth_pubkey: None, + nip05: nip05.map(|x| nauthz_grpc::event_request::Nip05Name::from(x)), + }) + .await?; + let reply = svr_res.into_inner(); + return Ok(Box::new(reply)); + } else { + return Err(Error::AuthzError); + } + } +} diff --git a/src/nip05.rs b/src/nip05.rs index f261e70..00f1689 100644 --- a/src/nip05.rs +++ b/src/nip05.rs @@ -42,8 +42,8 @@ pub struct Verifier { /// A NIP-05 identifier is a local part and domain. #[derive(PartialEq, Eq, Debug, Clone)] pub struct Nip05Name { - local: String, - domain: String, + pub local: String, + pub domain: String, } impl Nip05Name { diff --git a/src/server.rs b/src/server.rs index be3eab7..b0026a0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -601,11 +601,13 @@ async fn nostr_server( // and how many it received from queries. let mut client_published_event_count: usize = 0; let mut client_received_event_count: usize = 0; + + let unspec = "".to_string(); info!("new client connection (cid: {}, ip: {:?})", cid, conn.ip()); - let origin = client_info.origin.unwrap_or_else(|| "".into()); + let origin = client_info.origin.as_ref().unwrap_or_else(|| &unspec); let user_agent = client_info - .user_agent - .unwrap_or_else(|| "".into()); + .user_agent.as_ref() + .unwrap_or_else(|| &unspec); info!( "cid: {}, origin: {:?}, user-agent: {:?}", cid, origin, user_agent @@ -736,7 +738,7 @@ async fn nostr_server( // check if the event is too far in the future. if e.is_valid_timestamp(settings.options.reject_future_seconds) { // Write this to the database. - let submit_event = SubmittedEvent { event: e.clone(), notice_tx: notice_tx.clone(), source_ip: conn.ip().to_string()}; + let submit_event = SubmittedEvent { event: e.clone(), notice_tx: notice_tx.clone(), source_ip: conn.ip().to_string(), origin: client_info.origin.clone(), user_agent: client_info.user_agent.clone()}; event_tx.send(submit_event).await.ok(); client_published_event_count += 1; } else {