Skip to content

Conversation

@RobGeada
Copy link
Contributor

@RobGeada RobGeada commented Nov 3, 2025

Description

Introduces api_token as a field to the client config struct:

      service:
        hostname: model.url.com/v1
        port: 8443
        api_token: MODEL_TOKEN

If set, the orchestrator will look for an environment variable matching the value, e.g., $MODEL_TOKEN. If it exists, it is injected as a bearer token in the HTTP request. In the example above, Authorization: Bearer $MODEL_TOKEN will be sent as a header in all requests to that client.

This enables:

  • communication with authenticated clients in scenarios where the orchestrator cannot receive authorization tokens from the calling client (e.g., when requests to the orchestrator are proxied by kube-rbac-proxy)
  • per-client API keys

Verification

Manual testing confirmed that this works as expected

Copy link
Collaborator

@declark1 declark1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming token values do not change during runtime, I would suggest reading the env var once at startup so that invalid values (env var does not exist) for api_key are caught when the config is deserialized.

A helper function can be used with serde's deserialize_with attribute for this. Here is one that can be added to utils.rs if you all want to do this.

// utils.rs

/// Serde helper to deserialize value from environment variable.
pub fn from_env<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    let env_name: Option<String> = Option::deserialize(deserializer)?;
    if let Some(env_name) = env_name {
        let value = std::env::var(&env_name)
            .map_err(|_| serde::de::Error::custom(format!("env var `{env_name}` not found")))?;
        Ok(Some(value))
    } else {
        Ok(None)
    }
}


#[cfg(test)]
mod tests {
    use serde::Deserialize;
    use serde_json::json;

    use super::from_env;

    #[derive(Debug, Deserialize)]
    pub struct Config {
        #[serde(default, deserialize_with = "from_env")]
        pub api_token: Option<String>,
    }

    #[test]
    fn test_from_env() -> Result<(), Box<dyn std::error::Error>> {
        // Test no value
        let config: Config = serde_json::from_value(json!({}))?;
        assert_eq!(config.api_token, None);

        // Test invalid value
        let config: Result<Config, serde_json::error::Error> = serde_json::from_value(json!({
            "api_token": "DOES_NOT_EXIST"
        }));
        assert!(config.is_err_and(|err| err.to_string() == "env var `DOES_NOT_EXIST` not found"));

        // Test valid value
        unsafe {
            std::env::set_var("CLIENT_API_TOKEN", "token");
        }
        let config: Config = serde_json::from_value(json!({
            "api_token": "CLIENT_API_TOKEN"
        }))?;
        assert_eq!(config.api_token, Some("token".into()));

        Ok(())
    }
}

To use with ServiceConfig, just import crate::utils::from_env and add the attribute, e.g.

pub struct ServiceConfig {
    // ...
    #[serde(default, deserialize_with = "from_env")]
    pub api_token: Option<String>,
}

HttpClient::inject_api_token() would then just use self.api_token instead of reading via std::env::var().

@RobGeada RobGeada force-pushed the IndividualAuthSpecification branch from daca788 to 6712b22 Compare November 17, 2025 11:48
@RobGeada RobGeada force-pushed the IndividualAuthSpecification branch from 6712b22 to f975ce9 Compare November 17, 2025 11:48
@RobGeada
Copy link
Contributor Author

@declark1 thanks for the review- I've incorporated your changes and re-verified that it all works as expected

Copy link
Collaborator

@declark1 declark1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@declark1 declark1 merged commit 289f959 into foundation-model-stack:main Nov 17, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants