kilork / keycloak

Keycloak REST API Client
The Unlicense
56 stars 31 forks source link

[Feature Request] Use `securefmt` to redact sensitive data #121

Open rblaine95 opened 1 week ago

rblaine95 commented 1 week ago

Hello, I've been using this library in an API I've been building and it's worked exceptionally well so far.

One of the crates I've been using is securefmt which redacts sensitive fields when I enable debug logging.

An example of using it:

use securefmt::Debug;

#[derive(Debug)]
struct SensitiveData {
    id: u8,
    #[sensitive]
    secret: u8
}

fn main() {
    println!("{:?}", SensitiveData { id: 1, secret: 42 })
}

Which will then output

SensitiveData { id: 1, secret: <redacted> }

I think it would be useful to add this to sensitive fields such as Client Secrets

kilork commented 1 week ago

Hi @rblaine95!

It is interesting idea, thank you! I am currently trying to avoid Debug in structures with sensitive data, but this maybe a better option.

If we have such cases in generated code, this is a little bit more tricky, cause we need to know from OpenAPI description if this field is sensitive or provide extra configuration for such fields.

Could you maybe elaborate on which fields exactly should be [redacted] in Debug?

rblaine95 commented 1 week ago

Hi @kilork

One use case I've run into is the secret field in ClientRepresentation In local development I realized I was debug logging the full ClientRepresentation and it included the secret:

let client = kc_client
    .realm_clients_post(
        "example-realm",
        ClientRepresentation {
            client_id: Some("example-client".to_string()),
            direct_access_grants_enabled: Some(true),
            name: Some("example-client".to_string()),
            public_client: Some(false),
            authorization_services_enabled: Some(true),
            service_accounts_enabled: Some(true),
            ..Default::default()
        },
    )
    .await
    .unwrap();
println!("{:#?}", client);

Which prints the following:

ClientRepresentation {
[... trim long output ...]
    client_authenticator_type: Some(
        "client-secret",
    ),
    client_id: Some(
        "example-client",
    ),
[... trim long output ...]
    secret: Some(
        "xeM3U1VMizbgCbIaVDHnqTkaoXkHe8FQ",
    ),
[... trim long output ...]
}

Scanning through the keycloak::types module, there are 6 structs with a secret field:

That's just searching for the word secret. There's probably more with other names (like value in CredentialRepresentation).

Scanning through the OpenAPI v3 Spec I don't really see anything about marking specific fields as sensitive.

However, one possible workaround could be for Keycloak to mark sensitive fields as password instead of string when generating their OpenAPI spec, maybe that can be used to hint "this is a sensitive string" when generating the Rust Client.

For example, from the Keycloak 25.0.0 OpenAPI Yaml :

openapi: 3.0.3
info:
  title: Keycloak Admin REST API
  description: This is a REST API reference for the Keycloak Admin REST API.
  version: "1.0"
[... trim unnecessary fields from very long doc...]
components:
  schemas:
    ClientRepresentation:
      properties:
        secret:
-          type: string
+          type: password

It looks like, unless Keycloak marks sensitive fields as sensitive when they generate their OpenAPI spec, there's potentially quite a bit of legwork to identify and mark all the sensitive fields.

A potential approach could be to have a list of known sensitive fields and to inject the #[sensitive] attribute after you've generated the Rust Client, maybe it can be added to the update.ts deno script?