Switching TLS/SSL certificates based on the data in session in a scenario

Hi,
I have a use case where I am fetching requests from a CSV file and calling a secured endpoint using SSL certificates. The requests are grouped by the type of client. For each client simulation, the key store is different. In Gatling, we can do one time configuration or use SSL certificates per user, but how can I switch certificates based on the data in the session? Gatling stores the SSL certificate in a session key attribute - gatling.http.ssl.sslContexts and object of io.gatling.http.util.SslContexts

I am just looking for specific input here on how I can create a new object of SslContexts and overwrite above attribute when I want to switch the SSL Keystore?

Thanks,
Ashwani

Hi @Ashwani!

Welcome aboard!

First time I see such a use case. Interesting!

As you mentioned, perUserKeyManagerFactory allow to give a function userId => KeyManagerFactory.

In your case, you need to know a data from the session.
So, you may have to put the association in a common global variable.

    private static Map<Long, String> KIND_BY_USER_ID = new ConcurrentHashMap<>();

In your scenario, after getting data from your CSV, you can put the data from your session into the global association

    ScenarioBuilder users = scenario("Users")
            .feed(myCsv)
            .exec(session -> {
                KIND_BY_USER_ID.put(session.userId(), session.getString("keyStore"));
                return session;
            })
            .exec(search, browse);

So, with a function that fetch the right KeyManagerFactory by your keyStore value, you can add the implementation in the protocol

    HttpProtocolBuilder httpProtocol =
        http.baseUrl("https://computer-database.gatling.io")
// [snip]
           .perUserKeyManagerFactory(userId -> keyManagerFactoryByUserKind.apply(KIND_BY_USER_ID.get(userId)));

What do you think?

Cheers!

Hi,
Thanks for the reply. I am studying this example. I guess it should work in my scenario where I have only one VU and lets say thousand requests. Out of them 500 requests need one certificate and 500 need other based on some field in the data. it does not not matter what field it is - user id or other session attribute. Right?

perUserKeyManagerFactory only takes the userId as an argument. But with the trick of global static variable, you can maintain an association map.

I am not sure how that will satisfy my use case. Example below is a set of requests in csv or db.

{json request1}, colum1, colum2, colum3(value =client1)
{json request2}, colum1, colum2, colum3(value =client1)
{json request3}, colum1, colum2, colum3(value =client2)
{json request4}, colum1, colum2, colum3(value =client2)

I have to use a different key store for client1 and client2. I am not sure how I will fit your example :thinking:

is there any solution to it @sbrevet ?

Hi @Ashwani,

Did you try what I suggested? With a global map between userId and your value (or directly your Keystore)?

Cheers!

I need to vary the certificate based on the data not on VU. I am not sure how your solution can satisfy my requirement. :thinking:

Hi @Ashwani,

You mentioned a CSV, and your code example show a feed.

From the first sentence of the document for feeders:

Inject data into your virtual users from an external source, eg a CSV file

So, yes, when you call feed, gatling put the corresponding data into the Session.

With that knowledge, your global map will contain the association between userId and column3 value (client1 or client2).

The lambda you will give to perUserKeyManagerFactory will be able to make the mapping from the userId through the global map to your column3 value in order to finally give the right keystore.

Cheers!

Ahh, ok. I think I understand now. I keep updating the key manager factory for the user based on the data and it should pick that one for current request. let me try it. Thanks

In code below where I am initializing per user key manager factory the map is not initialized yet and null is passed to the getKeyManagerFactory() method. I guess it is called when initializing not when each request is getting executed. Am I doing something wrong here? I have only one VU

Below is portion of my code.

private  Map<Long, String> clientByuserId = new ConcurrentHashMap<>();
    ChainBuilder search = repeat(numberOfRecords, "n").on((feed(jdbcFeeder).exec(session -> {
                String orgId = session.get("msg_org");
                logger.debug("Org is {}", orgId);
                clientByuserId.put(session.userId(), orgId);
                return session;
            }).exec(http("GetDemo").post(getDemoURI).body(StringBody("#{msg_request}")).check(status().is(200))
                    .check(bodyString().saveAs("GDResponse")))
            .exec(session -> {
                String response = session.get("GDResponse");
                logger.debug("Response body: \n" + response);
                return session;
            }).pause(pauseDuration)));


    HttpProtocolBuilder httpProtocol = http.baseUrl(host).contentTypeHeader("application/soap+xml")
// null is getting passed as map is not initialized yet. 
            .perUserKeyManagerFactory(userId -> getKeyManagerFactory(clientByuserId.get(userId)));

 private KeyManagerFactory getKeyManagerFactory(String orgId) {
        return KeyManagerFactoryCache.getKeyManagerFactory(orgId);
    }

Hi @Ashwani,

Okay… I was wrong during the whole time! As I said it’s the first time I see such a use case.

Gatling’s creator (Hello @slandelle ^^) only gave access to the userId because KeyManagerFactory is initialized during Virtual User creation so before the first step. Indeed, as a real user, you configure your browser only once and navigate after. As gatling simulate the browser behavior, it will reuse opened connection for a user.

So, I need more context for your use case.

In your csv sample above:

You have only 2 clients, is it only for sample sake or is it a realistic case?
In case of small amount of keystore to manage, I think you should configure the protocol at the scenario level.

Something like:

  ScenarioBuilder createScenario(String name, FeederBuilder<String> feeder) {
    return scenarion(name)
      .feed(feeder)
      .exec(/// your scenario)
  }

  {
    setUp(
      createScenario("Scenario for client1", csv("forClient1.csv"))
        .injectOpen(constantUser(1))
        .protocol(createProtocol("client1")),
      createScenario("Scenario for client2", csv("forClient2.csv"))
        .injectOpen(constantUser(1))
        .protocol(createProtocol("client2")),
    )
  }

Otherwise, we will have to dig deeper in the rabbit hole…
The KeyManagerFactory is instanciated once at the begin of the life of the Virtual User. But the key is asked only when needed.
So, the global map trick may still be of use. But not during the instanciation of the KeyManager, but during the call of its methods.
You will have to create a KeyManagerFactory that will generate KeyManager that will hold the userId
And during the KeyManager life, it will base its answers from the global map.

I’m not sure I am very clear… but it’s quite an unusual topic. (I like that)

Is it possible to better understand your use case?

Cheers!

My understanding is that you have different types of users, each with a different type of keystore.
With Gatling, you can’t have a given virtual user pick a different keystore at runetime depending on the request. The single keystore is created once for each virtual user when it gets started.
As a result, you cannot mix requests requiring different keystores for a given virtual user.
If you want multiple keystores, you must have distinct virtual users.

In short, you need to sort your requests so you have virtual users that only simulate a given type of client.

Ok, thanks @slandelle and @sbrevet

We have exposed an API which is called by multiple clients/orgs/subscribers/partners whatever you want to call. If we have 10 partners, each one have different key store. I am building a solution where I collect all the requests of all the partners in DB and generate a load from that. I cannot just retrieve the request and send it as the request can belong to different client(of course I will group them as I showed in example) using different key store(I have common trust store). Possible solutions are

  1. Run multiple instances from different machines. Each machine configured for one type of client and keystore(Simplest solution)
  2. On one machine switch the keystore at runtime when a request belong to different client(most flexible)
  3. This one if possible may be a solution which is not bad. Configure 2-3 scenarios with different protocol configuration for 2-3 organizations/clients reducing the instances I have to run.

I tried with 3 option, using per user key manager factory and it seems to be working. Thanks a lot.

It will be great if flexibility is provided at the level of each request though I know it will be inefficient but one can optimize by sorting the records.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.