cduvray / jwt-authorizer

JWT authorization layer for Axum.
MIT License
68 stars 21 forks source link

`error decoding response body: EOF while parsing a value at line 1 column 0` #55

Open lcmgh opened 3 weeks ago

lcmgh commented 3 weeks ago

We are facing the issue that on some machines we do not face any issues while on other the authorizer throws this:

2024-08-20T10:21:01.829428Z  INFO jwt_authorizer::layer: err: JwksRefreshError("error decoding response body: EOF while parsing a value at line 1 column 0")
2024-08-20T10:21:01.829484Z ERROR jwt_authorizer::error: AuthErrors::JwksRefreshError: error decoding response body: EOF while parsing a value at line 1 column 0
2024-08-20T10:21:01.833785Z  INFO jwt_authorizer::layer: err: InvalidKeyAlg(RS256)
...
2024-08-20T10:21:01.887523Z  INFO jwt_authorizer::layer: err: InvalidKeyAlg(RS256)

I am not sure here why we get a parsing error AND a InvalidKeyAlg in here. Is there some kind of OS dependency necessary to make RS256 work?!

lcmgh commented 2 weeks ago

On my macOS machine it works fine, on colleague's machine not. The token that we are sending over also looks good.

cduvray commented 2 weeks ago

Hi, this is a signing key refresh error, are you sure that your jwks endpoint is available (or correct in you .well-known/openid-configuration)? This depends of you key store refresh strategie,

lcmgh commented 2 weeks ago

Some colleagues are facing this issue always on their Mac Machine while it always works fine on mine.

I am actually spawning a mock server

  /// Spawns tokio task that runs OIDC mock web server to serve public keys and well-known config.
  ///
  /// Endpoints:
  /// * `<address>/.well-known/openid-configuration`: Serves `tests/config/openid-configuration.json`
  /// * `<address>/keys`: Serves public keys from `tests/config/public1.jwks`
  pub async fn spawn_oidc_server_task(addr: SocketAddr) {
      #[derive(Clone)]
      struct AppState {
          pub port: u16,
      }

      async fn oidc_key_handler() -> &'static str {
          include_str!("../tests/config/public1.jwks")
      }

      async fn oidc_well_known_handler(State(state): State<AppState>) -> Json<Value> {
          let mut c: Value =
              serde_json::from_str(include_str!("../tests/config/openid-configuration.json"))
                  .unwrap();
          c["issuer"] = format!("http://localhost:{}", state.port).into();
          c["jwks_uri"] = format!("http://localhost:{}/keys", state.port).into();
          Json(c)
      }

      // Start fake OIDC server that just serves keys
      let oidc_server = Router::new().route("/keys", get(oidc_key_handler)).route(
          "/.well-known/openid-configuration",
          get(oidc_well_known_handler).with_state(AppState { port: addr.port() }),
      );
      tokio::task::spawn(async move {
          let listener = TcpListener::bind(&addr).await.unwrap();
          axum::serve(listener, oidc_server.into_make_service())
              .await
              .expect("failed to spawn oidc server");
      });
  }

During tests client generates tokens via

 /// Creates authentication token signed by server `fn spawn_oidc_server_task`.
    pub fn create_signed_token(
        aud: &str,
        namespace: &str,
        service_account_name: &str,
        sub: &str,
        provider_url: &str,
    ) -> Token<Header, KubernetesClaims, Signed> {
        let pem = include_bytes!("../tests/config/rsa-private1.pem");
        let key = PKey::private_key_from_pem(pem).unwrap();

        let algorithm = PKeyWithDigest {
            digest: MessageDigest::sha256(),
            key: key.clone(),
        };

        let header = Header {
            algorithm: AlgorithmType::Rs256,
            ..Default::default()
        };

        // Timetamp now + 1 hour
        let exp = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs()
            + 84000;
        let exp_str = exp.to_string();
        let nbf = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs()
            - 100;
        let nbf_str = nbf.to_string();

        let claims = KubernetesClaims {
            iss: Some(provider_url.to_string()),
            sub: Some(sub.to_string()),
            aud: Some(CustomOneOrArray::Array(vec![aud.to_string()])),
            exp: Some(serde_json::from_str(&exp_str).unwrap()),
            nbf: Some(serde_json::from_str(&nbf_str).unwrap()),
            iat: Some(serde_json::from_str(&nbf_str).unwrap()),
            jti: None,
            kubernetes_io: Some(KubernetesClaim {
                namespace: namespace.to_string(),
                pod: PodClaim {
                    name: "amui-85676447b8-qwkd5".to_string(),
                    uid: "008da9e0-e53c-43b4-b2b1-a0a85d824915".to_string(),
                },
                serviceaccount: ServiceAccountClaim {
                    name: service_account_name.to_string(),
                    uid: "d13b6723-6dda-4937-8623-c6fb798bda09".to_string(),
                },
            }),
        };

        Token::new(header, claims)
            .sign_with_key(&algorithm)
            .unwrap()
    }

openid-configuration.json

{
    "issuer": "http://localhost:3000",
    "jwks_uri": "http://localhost:3000/keys",
    "authorization_endpoint": "urn:kubernetes:programmatic_authorization",
    "response_types_supported": [
        "id_token"
    ],
    "subject_types_supported": [
        "public"
    ],
    "claims_supported": [
        "sub",
        "iss"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ]
}
cduvray commented 2 weeks ago

The message " EOF while parsing a value at line 1 column 0" comes from serde_json when you are trying to parse an empty string.

 #[derive(Deserialize)]
struct XY {}
 let r: Result<XY, serde_json::Error> = serde_json::from_str("");
 assert_eq!(r.err().unwrap().to_string(), "EOF while parsing a value at line 1 column 0");  

I think the jwks endpoint must be sending an empty response, (maybe with an error status, that is not well checked and reported by jwt-authorizer ... should be fixed), have you proxies or some proxy configuration on your machines? oran anti virus that can intercept responses?

cduvray commented 1 week ago

This new commit 10a926c should be helpful to better identify the cause of such an issue, in fact before if the jwks endpoint responded with an error status it was ignored and the content (maybe empty) parsed thus resulting in a json parsing error.