-
Notifications
You must be signed in to change notification settings - Fork 10
Support client side tls certificates reload #66
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
base: master
Are you sure you want to change the base?
Conversation
2bc7594
to
a7a17be
Compare
@behos Would you mind take a look ? Does it solve your concerns ? I included a file-based certs in test. zookeeper-client-rust/src/tls/client.rs Lines 352 to 452 in a7a17be
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, the file based implementation can either be exposed here or delegated to external services.
src/tls/client.rs
Outdated
struct FileBasedDynamicCerts { | ||
ca: Ca, | ||
dir: TempDir, | ||
certs: TlsDynamicCerts, | ||
feedback: UnboundedReceiver<()>, | ||
_task: TaskHandle<()>, | ||
} | ||
|
||
struct EventSender { | ||
sender: UnboundedSender<Event>, | ||
} | ||
|
||
impl notify::EventHandler for EventSender { | ||
fn handle_event(&mut self, event: Result<Event, notify::Error>) { | ||
if let Ok(event) = event { | ||
self.sender.unbounded_send(event).unwrap(); | ||
} | ||
} | ||
} | ||
|
||
impl FileBasedDynamicCerts { | ||
pub fn new(ca: Ca, client_name: &str) -> Self { | ||
let dir = TempDir::new().unwrap(); | ||
Self::generate_cert(&ca, dir.path(), client_name); | ||
let (certs, feedback, _task) = Self::load_dynamic_certs(dir.path()); | ||
Self { ca, dir, certs, feedback, _task } | ||
} | ||
|
||
fn load_dynamic_certs(dir: &Path) -> (TlsDynamicCerts, UnboundedReceiver<()>, TaskHandle<()>) { | ||
let cert_path = dir.join("cert.pem").to_path_buf(); | ||
let key_path = dir.join("cert.key.pem").to_path_buf(); | ||
|
||
let mut cert_modified = std::fs::metadata(&cert_path).unwrap().modified().unwrap(); | ||
let mut key_modified = std::fs::metadata(&key_path).unwrap().modified().unwrap(); | ||
let client_cert = std::fs::read_to_string(&cert_path).unwrap(); | ||
let client_key = std::fs::read_to_string(&key_path).unwrap(); | ||
|
||
let dynamic_certs = | ||
TlsDynamicCerts::new(TlsCerts::default().with_pem_identity(&client_cert, &client_key).unwrap()); | ||
let dynamic_certs_updator = dynamic_certs.clone(); | ||
|
||
let (feedback_sender, feedback_receiver) = mpsc::unbounded(); | ||
let task = asyncs::spawn(async move { | ||
let (tx, mut rx) = mpsc::unbounded(); | ||
let mut watcher = notify::recommended_watcher(EventSender { sender: tx }).unwrap(); | ||
watcher.watch(&cert_path, RecursiveMode::NonRecursive).unwrap(); | ||
watcher.watch(&key_path, RecursiveMode::NonRecursive).unwrap(); | ||
while rx.next().await.is_some() { | ||
let updated_cert_modified = std::fs::metadata(&cert_path).unwrap().modified().unwrap(); | ||
let updated_key_modified = std::fs::metadata(&key_path).unwrap().modified().unwrap(); | ||
if updated_cert_modified <= cert_modified || updated_key_modified <= key_modified { | ||
continue; | ||
} | ||
cert_modified = updated_cert_modified; | ||
key_modified = updated_key_modified; | ||
let client_cert = std::fs::read_to_string(&cert_path).unwrap(); | ||
let client_key = std::fs::read_to_string(&key_path).unwrap(); | ||
dynamic_certs_updator | ||
.update(TlsCerts::default().with_pem_identity(&client_cert, &client_key).unwrap()); | ||
feedback_sender.unbounded_send(()).unwrap(); | ||
} | ||
}) | ||
.attach(); | ||
(dynamic_certs, feedback_receiver, task) | ||
} | ||
|
||
fn generate_cert(ca: &Ca, dir: &Path, name: &str) { | ||
let client_cert = generate_client_cert(name, &ca.issuer); | ||
let file = AtomicWriteFile::open(dir.join("cert.pem")).unwrap(); | ||
write!(&file, "{}", client_cert.cert.pem()).unwrap(); | ||
file.commit().unwrap(); | ||
|
||
let file = AtomicWriteFile::open(dir.join("cert.key.pem")).unwrap(); | ||
write!(&file, "{}", client_cert.signing_key.serialize_pem()).unwrap(); | ||
file.commit().unwrap(); | ||
} | ||
|
||
pub async fn regenerate_cert(&mut self, client_name: &str) { | ||
Self::generate_cert(&self.ca, self.dir.path(), client_name); | ||
self.feedback.next().await; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's worth having a FileBased implementation into the main package so that it's easily reusable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think such an official file based implementation may have biased on the change detection. And, worse, it will propgate this to applications. I think we should avoid such a thing in library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good to me! The example looks clean enough to use as a basis for an implementation anyway. Thanks
/// Cell to keep up to date [TlsCerts]. | ||
#[derive(Clone, Debug)] | ||
pub struct TlsDynamicCerts { | ||
certs: Arc<RwLock<(u64, Arc<TlsCerts>)>>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to use this from the branch and it seems it's not visible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My fault! I have referenced it in integration test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I was able to create and test a simple file-based reloader that can be plugged into the new structs. It works great
15be32d
to
e1a6e06
Compare
This allows creating a client with dynamic tls certificates. When created this way, on reconnection the client will use latest tls certificates. This allows us to have auto-reloading of refreshed certificates stored anywhere in client side. Resolves #59.
This allows creating a client with dynamic tls certificates. When created this way, on reconnection the client will use latest tls certificates.
This allows us to have auto-reloading of refreshed certificates stored anywhere in client side.
Resolves #59.