Skip to content

Commit

Permalink
feat: improving tls flow (#34)
Browse files Browse the repository at this point in the history
## Motivation

I'm writing an article on how easy it's suppose to be to setup TLS/SSL
with ScyllaDB and one of the examples which I want to add is using JS.
ATM the driver only support the Certificate without Keys/Truststore and
this pull request adds this specific support.


```js
// Before 
const cluster = new Cluster({
    nodes,
    ssl: {
        caFilepath: "/your/path/to/certificates/client_truststore.pem",
        verifyMode: VerifyMode.Peer,
    }
});


// After
const cluster = new Cluster({
    nodes,
    ssl: {
        enabled: true, // Feature Flag
        truststoreFilepath: "/your/path/to/certificates/client_cert.pem", // Added field
        privateKeyFilepath: "/your/path/to/certificates/client_key.pem", // Added field
        caFilepath: "/your/path/to/certificates/client_truststore.pem",
        verifyMode: VerifyMode.Peer,
    }
});
```
IMHO I don't know if this feature flag is useful, but at least for me
seems more like a easy way to turn it on/off. So, please let me know
your thoughts on that.

> [!TIP]
> You can test with [this
sample](https://github.com/DanielHe4rt/scylladb-role-tls-auth) by
running `make setup` and then pointing your keys **absolute path** at
the SSL object. Also, don't forget to switch your port to `9142` at the
connection string.

## Changes

- [x] TLS/SSL with Keystore and Private Keys
- [x] Feature flag to enable/disable SSL.  
- [x] Simple example on how to use it.
  • Loading branch information
danielhe4rt authored Aug 6, 2024
1 parent c389187 commit 75a8336
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 41 deletions.
41 changes: 41 additions & 0 deletions examples/tls.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Cluster, VerifyMode} from "../index.js"

const nodes = process.env.CLUSTER_NODES?.split(",") ?? ["localhost:9142"];
console.log(`Connecting to ${nodes}`);

const cluster = new Cluster({
nodes,
ssl: {
enabled: true,
truststoreFilepath: "/your/path/to/certificates/client_cert.pem",
privateKeyFilepath: "/your/path/to/certificates/client_key.pem",
caFilepath: "/your/path/to/certificates/client_truststore.pem",
verifyMode: VerifyMode.Peer,
}
});

const session = await cluster.connect();

interface ConnectedClient {
address: String,
port: number,
username: String,
driver_name: String,
driver_version: String,
}

// @ts-ignore
let result = await session.execute<ConnectedClient>("SELECT address, port, username, driver_name, driver_version FROM system.clients");

console.log(result)
// [
// {
// address: '127.0.0.1',
// driver_name: 'scylla-rust-driver',
// driver_version: '0.10.1',
// port: 58846,
// username: 'developer'
// }
// ]


21 changes: 12 additions & 9 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export interface Auth {
password: string
}
export interface Ssl {
caFilepath: string
enabled: boolean
caFilepath?: string
privateKeyFilepath?: string
truststoreFilepath?: string
verifyMode?: VerifyMode
}
export const enum VerifyMode {
Expand Down Expand Up @@ -89,7 +92,7 @@ export interface ScyllaMaterializedView {
baseTableName: string
}
export type ScyllaCluster = Cluster
export class Cluster {
export declare class Cluster {
/**
* Object config is in the format:
* {
Expand All @@ -108,7 +111,7 @@ export type ScyllaBatchStatement = BatchStatement
* These statements can be simple or prepared.
* Only INSERT, UPDATE and DELETE statements are allowed.
*/
export class BatchStatement {
export declare class BatchStatement {
constructor()
/**
* Appends a statement to the batch.
Expand All @@ -118,17 +121,17 @@ export class BatchStatement {
*/
appendStatement(statement: Query | PreparedStatement): void
}
export class PreparedStatement {
export declare class PreparedStatement {
setConsistency(consistency: Consistency): void
setSerialConsistency(serialConsistency: SerialConsistency): void
}
export class Query {
export declare class Query {
constructor(query: string)
setConsistency(consistency: Consistency): void
setSerialConsistency(serialConsistency: SerialConsistency): void
setPageSize(pageSize: number): void
}
export class Metrics {
export declare class Metrics {
/** Returns counter for nonpaged queries */
getQueriesNum(): bigint
/** Returns counter for pages requested in paged queries */
Expand All @@ -148,7 +151,7 @@ export class Metrics {
*/
getLatencyPercentileMs(percentile: number): bigint
}
export class ScyllaSession {
export declare class ScyllaSession {
metrics(): Metrics
getClusterData(): Promise<ScyllaClusterData>
execute(query: string | Query | PreparedStatement, parameters?: Array<number | string | Uuid> | undefined | null): Promise<any>
Expand Down Expand Up @@ -261,14 +264,14 @@ export class ScyllaSession {
awaitSchemaAgreement(): Promise<Uuid>
checkSchemaAgreement(): Promise<boolean>
}
export class ScyllaClusterData {
export declare class ScyllaClusterData {
/**
* Access keyspaces details collected by the driver Driver collects various schema details like
* tables, partitioners, columns, types. They can be read using this method
*/
getKeyspaceInfo(): Record<string, ScyllaKeyspace> | null
}
export class Uuid {
export declare class Uuid {
/** Generates a random UUID v4. */
static randomV4(): Uuid
/** Parses a UUID from a string. It may fail if the string is not a valid UUID. */
Expand Down
91 changes: 59 additions & 32 deletions src/cluster/scylla_cluster.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time::Duration;

use napi::Either;
use openssl::ssl::SslContextBuilder;
use openssl::ssl::{SslContextBuilder, SslFiletype};

use crate::{
cluster::{
Expand Down Expand Up @@ -40,8 +40,12 @@ pub struct Auth {
#[napi(object)]
#[derive(Clone)]
pub struct Ssl {
pub ca_filepath: String,
pub enabled: bool,
pub ca_filepath: Option<String>,
pub private_key_filepath: Option<String>,
pub truststore_filepath: Option<String>,
pub verify_mode: Option<VerifyMode>,
// SSL Filetype: PEM / ASN1
}

#[napi]
Expand Down Expand Up @@ -192,43 +196,66 @@ impl ScyllaCluster {
}

if let Some(ssl) = ssl? {
let ssl_builder = SslContextBuilder::new(openssl::ssl::SslMethod::tls());
if ssl.enabled {
let ssl_builder = SslContextBuilder::new(openssl::ssl::SslMethod::tls());

if let Err(err) = ssl_builder {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to create SSL context: {}", err),
));
}
if let Err(err) = ssl_builder {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to create SSL context: {}", err),
));
}

// Safe to unwrap because we checked for Err above
let mut ssl_builder = ssl_builder.unwrap();
// Safe to unwrap because we checked for Err above
let mut ssl_builder = ssl_builder.unwrap();

if let Some(verify_mode) = ssl.verify_mode {
ssl_builder.set_verify(match verify_mode {
VerifyMode::None => openssl::ssl::SslVerifyMode::NONE,
VerifyMode::Peer => openssl::ssl::SslVerifyMode::PEER,
});
} else {
ssl_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
}
if let Some(verify_mode) = ssl.verify_mode {
ssl_builder.set_verify(match verify_mode {
VerifyMode::None => openssl::ssl::SslVerifyMode::NONE,
VerifyMode::Peer => openssl::ssl::SslVerifyMode::PEER,
});
} else {
ssl_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
}

if let Err(err) = ssl_builder.set_ca_file(ssl.ca_filepath) {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to set CA file: {}", err),
));
}
if let Some(private_key_filepath) = ssl.private_key_filepath {
if let Err(err) = ssl_builder.set_private_key_file(private_key_filepath, SslFiletype::PEM)
{
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to set private key file: {}", err),
));
}
}

if let Some(auto_await_schema_agreement) = self.auto_await_schema_agreement {
builder = builder.auto_await_schema_agreement(auto_await_schema_agreement);
}
if let Some(truststore_filepath) = ssl.truststore_filepath {
if let Err(err) = ssl_builder.set_certificate_chain_file(truststore_filepath) {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to set truststore file: {}", err),
));
}
}

if let Some(schema_agreement_interval) = self.schema_agreement_interval {
builder = builder.schema_agreement_interval(schema_agreement_interval);
}
if let Some(ca_filepath) = ssl.ca_filepath {
if let Err(err) = ssl_builder.set_ca_file(ca_filepath) {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Failed to set CA file: {}", err),
));
}
}

if let Some(auto_await_schema_agreement) = self.auto_await_schema_agreement {
builder = builder.auto_await_schema_agreement(auto_await_schema_agreement);
}

if let Some(schema_agreement_interval) = self.schema_agreement_interval {
builder = builder.schema_agreement_interval(schema_agreement_interval);
}

builder = builder.ssl_context(Some(ssl_builder.build()));
builder = builder.ssl_context(Some(ssl_builder.build()));
}
}

if let Some(default_execution_profile) = &self.default_execution_profile {
Expand Down

0 comments on commit 75a8336

Please sign in to comment.