mehcode/config-rs

Support for .pem files

Closed this issue · 3 comments

In my current project, it was decided to use a .pem file to save a private RSA key. I would like to know it it is possible to assign the content of such file as a source to be defined as the value of access_token_private_key field.

Here is my settings struct:

/// Global settings for the exposing all preconfigured variables
#[derive(serde::Deserialize, Clone)]
pub struct Settings {
    pub application: ApplicationSettings,
    pub debug: bool,
    pub redis: RedisSettings,
    pub secret: Secret,
    pub email: EmailSettings,
    pub frontend_url: String
}

I wanna setup access_token_private_key field

#[derive(serde::Deserialize, Clone)]
pub struct Secret {
    pub secret_key: String,
    pub token_expiration: i64,
    pub hmac_secret: String,

    pub access_token_private_key: String,
    pub access_token_public_key: String,
    pub access_token_expired_in: String,
    pub access_token_maxage: i64
}

Here is a function that I call to retrieve my settings

pub fn get_settings() -> Result<Settings, config::ConfigError> {
    let base_path = std::env::current_dir().expect("Failed to determine the current directory");
    let settings_directory = base_path.join("settings");

    //Cert Path
    let cert_directory = base_path.join("settings");

    // Detect the running environment.
    // Default to `development` if unspecified.
    let environment: Environment = std::env::var("APP_ENVIRONMENT")
        .unwrap_or_else(|_| "development".into())
        .try_into()
        .expect("Failed to parse APP_ENVIRONMENT.");
    let environment_filename = format!("{}.yaml", environment.as_str());
    let settings = config::Config::builder()
        .add_source(config::File::from(settings_directory.join("base.yaml")))
        .add_source(config::File::from(
            settings_directory.join(environment_filename),
        ))
        // Add in settings from environment variables (with a prefix of APP and '__' as separator)
        // E.g. `APP_APPLICATION__PORT=5001 would set `Settings.application.port`
        .add_source(
            config::Environment::with_prefix("APP")
                .prefix_separator("_")
                .separator("__"),
        )
        // Add private/public key value
        .add_source(????)
        .build()?;

    settings.try_deserialize::<Settings>()
}

Hi! So this should be possible with the config crate, yes. Have a look at the Format trait, you probably need to implement this for a custom type of yours representing your pem file format. You should then be able to populate a Source with your format and pass this into the builder for loading your pem file.

I hope I didn't overlook anything in your example, but that's as far as I can see right now your path.

Thanks for answer! I have taken the approach that you have mention in your previous response. I just took a while to figure it out but ended up with this:

#[derive(Debug, Clone)]
pub struct PemFile;

impl Format for PemFile {
    fn parse(
        &self,
        uri: Option<&String>,
        text: &str,
    ) -> Result<Map<String, config::Value>, Box<dyn std::error::Error + Send + Sync>> {
        let mut result = Map::new();

        let key = match text.contains("PRIVATE") {
            true => String::from("secret.access_token_private_key"),
            false => String::from("secret.access_token_public_key"),
        };

        let encoded_key = base64::engine::general_purpose::STANDARD.encode::<String>(text.into());

        result.insert(key.clone(), Value::new(uri, ValueKind::String(encoded_key)));

        result.insert(format!("{}_raw", key), Value::new(uri, ValueKind::String(text.into())));

        Ok(result)
    }
}

static PEM_EXT: Vec<&'static str> = vec![];
impl FileStoredFormat for PemFile {
    fn file_extensions(&self) -> &'static [&'static str] {
        &PEM_EXT
    }
}

Then in inside get_settings function I did this:

let settings = config::Config::builder()
        .add_source(config::File::from(settings_directory.join("base.yaml")))
        //Load Keys from PEM files in cert folder
        .add_source(
            config::File::new(
                cert_directory
                    .join("private.pem")
                    .to_str()
                    .expect("unable to convert into string"),
                PemFile,
            )
            .required(false),
        )
        .add_source(
            config::File::new(
                cert_directory
                    .join("public.pem")
                    .to_str()
                    .expect("unable to convert into string"),
                PemFile,
            )
            .required(false),
        )

This is working just fine! I think that documentation regarding custom formats should be improved. If it is of your interest, I can provide previous code as an example of a custom format for .pem files.

Please do, I would love to see more examples and documentation for this crate!