Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support tls sender #7

Merged
merged 1 commit into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ all-features = true
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
rustdoc-args = ["--cfg", "docsrs"]

[features]
native-tls = ["dep:native-tls"]

[dependencies]
jiff = { version = "0.1.14" }

# Optional dependencies
native-tls = { version = "0.2.12", optional = true }

[target.'cfg(unix)'.dependencies]
cfg-if = { version = "1.0.0" }
nix = { version = "0.29.0", features = ["hostname"] }
Expand All @@ -48,6 +54,12 @@ doc-scrape-examples = true
name = "tcp_sender"
path = "examples/tcp_sender.rs"

[[example]]
doc-scrape-examples = true
name = "tls_sender"
path = "examples/tls_sender.rs"
required-features = ["native-tls"]

[[example]]
doc-scrape-examples = true
name = "udp_sender"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ This crate is built against the latest stable release, and its minimum supported

The policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if Fasyslog 1.0 requires Rust 1.20.0, then Fasyslog 1.0.z for all values of z will also require Rust 1.20.0 or newer. However, Fasyslog 1.y for y > 0 may require a newer minimum version of Rust.

## When to release a 1.0 version

I'm fine with the current API design and ready for a 1.0 release. Just leave a few months for feedback on the crate's usability. If you have any suggestions or feedback, please open an issue.

I'm going to release a 1.0 version as early as 2025-01.

## License

This project is licensed under [Apache License, Version 2.0](LICENSE).
28 changes: 28 additions & 0 deletions examples/tls_sender.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use fasyslog::Severity;

fn main() {
let mut sender = fasyslog::sender::tls_well_known("127.0.0.1").unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test locally with:

    let mut sender = fasyslog::sender::tls_with("127.0.0.1:6514", "127.0.0.1", {
        let mut builder = TlsConnector::builder();
        builder.danger_accept_invalid_certs(true);
        builder
    })
    .unwrap();

and vector-rs with syslog source:

sources:
  in:
    type: syslog
    address: 127.0.0.1:6514
    mode: tcp
    path: /path/to/socket
    tls:
      enabled: true
      crt_file: /path/to/certs/identity.p12
      key_file: /path/to/certs/identity.p12
      key_pass: mypass

sinks:
  out:
    inputs:
      - "in"
    type: "console"
    encoding:
      codec: "text"

let mut generator = names::Generator::default();
for _ in 0..100 {
let name = generator.next().unwrap();
let message = format!("Hello, {name}!");
sender
.send_rfc5424(Severity::ERROR, None::<String>, Vec::new(), message)
.unwrap();
}
sender.flush().unwrap();
}
26 changes: 24 additions & 2 deletions src/sender/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ macro_rules! impl_syslog_sender_common {
&mut self,
severity: $crate::Severity,
message: M,
) -> io::Result<()> {
) -> std::io::Result<()> {
let message = self.context.format_rfc3164(severity, Some(message));
self.send_formatted(message.to_string().as_bytes())
}
Expand All @@ -32,7 +32,7 @@ macro_rules! impl_syslog_sender_common {
msgid: Option<S>,
elements: Vec<$crate::SDElement>,
message: M,
) -> io::Result<()> {
) -> std::io::Result<()> {
let message = self
.context
.format_rfc5424(severity, msgid, elements, Some(message));
Expand All @@ -43,3 +43,25 @@ macro_rules! impl_syslog_sender_common {
}

pub(crate) use impl_syslog_sender_common;

macro_rules! impl_syslog_stream_send_formatted {
($sender:ident) => {
impl $sender {
/// Send a formatted message to the stream.
pub fn send_formatted(&mut self, message: &[u8]) -> std::io::Result<()> {
use std::io::Write;
self.writer.write_all(message)?;
self.writer.write_all(self.postfix.as_bytes())?;
Ok(())
}

/// Flush the stream.
pub fn flush(&mut self) -> std::io::Result<()> {
use std::io::Write;
self.writer.flush()
}
}
};
}

pub(crate) use impl_syslog_stream_send_formatted;
15 changes: 15 additions & 0 deletions src/sender/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ mod unix;
#[cfg(unix)]
pub use unix::*;

#[cfg(feature = "native-tls")]
mod native_tls;
#[cfg(feature = "native-tls")]
pub use native_tls::*;

mod tcp;
pub use tcp::*;

Expand All @@ -38,6 +43,8 @@ pub(crate) mod internal;
pub enum SyslogSender {
Tcp(TcpSender),
Udp(UdpSender),
#[cfg(feature = "native-tls")]
Tls(TlsSender),
#[cfg(unix)]
UnixDatagram(UnixDatagramSender),
#[cfg(unix)]
Expand All @@ -54,6 +61,8 @@ impl SyslogSender {
match self {
SyslogSender::Tcp(sender) => sender.send_rfc3164(severity, message),
SyslogSender::Udp(sender) => sender.send_rfc3164(severity, message),
#[cfg(feature = "native-tls")]
SyslogSender::Tls(sender) => sender.send_rfc3164(severity, message),
#[cfg(unix)]
SyslogSender::UnixDatagram(sender) => sender.send_rfc3164(severity, message),
#[cfg(unix)]
Expand All @@ -72,6 +81,8 @@ impl SyslogSender {
match self {
SyslogSender::Tcp(sender) => sender.send_rfc5424(severity, msgid, elements, message),
SyslogSender::Udp(sender) => sender.send_rfc5424(severity, msgid, elements, message),
#[cfg(feature = "native-tls")]
SyslogSender::Tls(sender) => sender.send_rfc5424(severity, msgid, elements, message),
#[cfg(unix)]
SyslogSender::UnixDatagram(sender) => {
sender.send_rfc5424(severity, msgid, elements, message)
Expand All @@ -88,6 +99,8 @@ impl SyslogSender {
match self {
SyslogSender::Tcp(sender) => sender.send_formatted(formatted),
SyslogSender::Udp(sender) => sender.send_formatted(formatted),
#[cfg(feature = "native-tls")]
SyslogSender::Tls(sender) => sender.send_formatted(formatted),
#[cfg(unix)]
SyslogSender::UnixDatagram(sender) => sender.send_formatted(formatted),
#[cfg(unix)]
Expand All @@ -108,6 +121,8 @@ impl SyslogSender {
match self {
SyslogSender::Tcp(sender) => sender.flush(),
SyslogSender::Udp(_) => Ok(()),
#[cfg(feature = "native-tls")]
SyslogSender::Tls(sender) => sender.flush(),
#[cfg(unix)]
SyslogSender::UnixDatagram(_) => Ok(()),
#[cfg(unix)]
Expand Down
102 changes: 102 additions & 0 deletions src/sender/native_tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2024 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::borrow::Cow;
use std::io;
use std::io::BufWriter;
use std::net::TcpStream;
use std::net::ToSocketAddrs;

use native_tls::TlsConnector;
use native_tls::TlsConnectorBuilder;
use native_tls::TlsStream;

use crate::format::SyslogContext;
use crate::sender::internal::impl_syslog_sender_common;
use crate::sender::internal::impl_syslog_stream_send_formatted;

/// Create a TLS sender that sends messages to the well-known port (6514).
///
/// See also [RFC-5425] §4.1 Port Assignment.
///
/// [RFC-5425]: https://datatracker.ietf.org/doc/html/rfc5425#section-4.1
pub fn tls_well_known<S: AsRef<str>>(domain: S) -> io::Result<TlsSender> {
let domain = domain.as_ref();
tls(format!("{domain}:6514"), domain)
}

/// Create a TLS sender that sends messages to the given address.
pub fn tls<A: ToSocketAddrs, S: AsRef<str>>(addr: A, domain: S) -> io::Result<TlsSender> {
tls_with(addr, domain, TlsConnector::builder())
}

/// Create a TLS sender that sends messages to the given address with certificate builder.
pub fn tls_with<A: ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
builder: TlsConnectorBuilder,
) -> io::Result<TlsSender> {
TlsSender::connect(addr, domain, builder)
}

/// A syslog sender that sends messages to a TCP socket over TLS.
#[derive(Debug)]
pub struct TlsSender {
writer: BufWriter<TlsStream<TcpStream>>,
context: SyslogContext,
postfix: Cow<'static, str>,
}

impl TlsSender {
/// Connect to a TCP socket over TLS at the given address.
pub fn connect<A: ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
builder: TlsConnectorBuilder,
) -> io::Result<Self> {
let domain = domain.as_ref();
let stream = TcpStream::connect(addr)?;
let connector = builder.build().map_err(io::Error::other)?;
let stream = connector
.connect(domain, stream)
.map_err(io::Error::other)?;
Ok(Self {
writer: BufWriter::new(stream),
context: SyslogContext::default(),
postfix: Cow::Borrowed("\r\n"),
})
}

/// Set the postfix when formatting Syslog message.
///
/// This is generally '\r\n' as defined in [RFC-6587] §3.4.2.
///
/// [RFC-6587]: https://datatracker.ietf.org/doc/html/rfc6587
pub fn set_postfix(&mut self, postfix: impl Into<Cow<'static, str>>) {
self.postfix = postfix.into();
}

/// Set the context when formatting Syslog message.
pub fn set_context(mut self, context: SyslogContext) {
self.context = context;
}

/// Mutate the context when formatting Syslog message.
pub fn mut_context(&mut self) -> &mut SyslogContext {
&mut self.context
}
}

impl_syslog_sender_common!(TlsSender);
impl_syslog_stream_send_formatted!(TlsSender);
16 changes: 2 additions & 14 deletions src/sender/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
use std::borrow::Cow;
use std::io;
use std::io::BufWriter;
use std::io::Write;
use std::net::TcpStream;
use std::net::ToSocketAddrs;

use crate::format::SyslogContext;
use crate::sender::internal::impl_syslog_sender_common;
use crate::sender::internal::impl_syslog_stream_send_formatted;

/// Create a TCP sender that sends messages to the well-known port (601).
///
Expand Down Expand Up @@ -73,19 +73,7 @@ impl TcpSender {
pub fn mut_context(&mut self) -> &mut SyslogContext {
&mut self.context
}

/// Send a pre-formatted message.
pub fn send_formatted(&mut self, formatted: &[u8]) -> io::Result<()> {
self.writer.write_all(formatted)?;
self.writer.write_all(self.postfix.as_bytes())?;
Ok(())
}

/// Flush the writer.
pub fn flush(&mut self) -> io::Result<()> {
use std::io::Write;
self.writer.flush()
}
}

impl_syslog_sender_common!(TcpSender);
impl_syslog_stream_send_formatted!(TcpSender);
16 changes: 2 additions & 14 deletions src/sender/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
use std::borrow::Cow;
use std::io;
use std::io::BufWriter;
use std::io::Write;
use std::os::unix::net::UnixDatagram;
use std::os::unix::net::UnixStream;
use std::path::Path;

use crate::format::SyslogContext;
use crate::sender::internal::impl_syslog_sender_common;
use crate::sender::internal::impl_syslog_stream_send_formatted;
use crate::sender::SyslogSender;

/// Create a Unix datagram sender that sends messages to the given path.
Expand Down Expand Up @@ -141,19 +141,7 @@ impl UnixStreamSender {
pub fn mut_context(&mut self) -> &mut SyslogContext {
&mut self.context
}

/// Send a pre-formatted message.
pub fn send_formatted(&mut self, formatted: &[u8]) -> io::Result<()> {
self.writer.write_all(formatted)?;
self.writer.write_all(self.postfix.as_bytes())?;
Ok(())
}

/// Flush the writer.
pub fn flush(&mut self) -> io::Result<()> {
use std::io::Write;
self.writer.flush()
}
}

impl_syslog_sender_common!(UnixStreamSender);
impl_syslog_stream_send_formatted!(UnixStreamSender);