Skip to content

Support credentials reloading when using with_credentials_file #1471

@eplightning

Description

@eplightning

Proposed change

Currently when using ConnectOptions::with_credentials_file, the passed credentials file will only be loaded once - during the with_credentials_file call - and then never again.
This means when credentials eventually become invalid (e.g. when exp claim in JWT was set), the client not be able to recover.

It is possible to workaround this problem via auth_callback, but it requires pulling a lot of extra dependencies and duplicating code that is already inside the crate:

static USER_CONFIG_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}\r?\n))")
        .unwrap()
});

fn extract_jwt_and_nkey_from_creds(contents: &str) -> Option<(&str, &str)> {
    let mut matches = USER_CONFIG_RE.captures_iter(contents);
    let jwt_capture = matches.next()?;
    let nkey_capture = matches.next()?;

    Some((jwt_capture.get(1)?.as_str(), nkey_capture.get(1)?.as_str()))
}

async fn dynamic_credentials_file_auth(
    creds_path: impl AsRef<Path>,
    nonce: Vec<u8>,
) -> std::result::Result<Auth, AuthError> {
    let contents = read_to_string(creds_path).await.map_err(AuthError::new)?;

    let (jwt, nkey) = extract_jwt_and_nkey_from_creds(&contents)
        .ok_or_else(|| AuthError::new("creds file not in a valid format"))?;

    let kp = KeyPair::from_seed(nkey).map_err(AuthError::new)?;
    let signed_nonce = kp.sign(&nonce).map_err(AuthError::new)?;

    let mut auth = Auth::new();
    auth.jwt = Some(jwt.to_owned());
    auth.signature = Some(signed_nonce);
    Ok(auth)
}

pub async fn connect_to_nats_refresh() -> Client {
    let opts = ConnectOptions::with_auth_callback(move |nonce| {
        async move { dynamic_credentials_file_auth("/tmp/nats.creds", nonce).await }
    });

    opts.connect("nats://localhost:4222").await.unwrap()
}

Use case

Allow credential rotation without requiring full application restart.

This is fully supported in other NATS SDKs (at least with the ones I tested: Go, NodeJS, Java and the old non-async Rust crate).

Contribution

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalEnhancement idea or proposal

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions