Skip to content

Commit

Permalink
Add Locust load test and README
Browse files Browse the repository at this point in the history
This adds the Locust file used to run a load test against Fulcio.

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Jan 4, 2022
1 parent 0df4239 commit 5cdd33f
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
48 changes: 48 additions & 0 deletions tools/loadtest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Fulcio Performance Test

## Overview

[Learn more about Locust](http://docs.locust.io/en/stable/index.html).

1. Install Locust with `pip3 install -r requirements.txt`
1. Fetch an identity token for a service account with `gcloud auth print-identity-token --audiences sigstore --impersonate-service-account <name>@<project-id>.iam.gserviceaccount.com --include-email`.
1. Start `locust`, configuring number of users, spawn rate, host, maximum QPS per user, and identity token.

## Running Locust

### Installation

Run `pip3 install -r requirements.txt`, which will install Locust and necessary libraries.

Confirm a successful install with `locust -V`, which should print the version. You may need to include `~/.local/bin` in your PATH.

### Fetching identity token

To fetch a certificate, you will need an OIDC token from one of the [OIDC issuers](https://github.com/sigstore/fulcio/blob/main/config/fulcio-config.yaml). One way is to fetch a token from Google. Note that you will need to install [`gcloud`](https://cloud.google.com/sdk/gcloud) and create a service account. A service account is necessary for the `--include-email` flag, which is needed to get an OIDC token with the correct format for Fulcio.

Run the following command, and record the output:

`gcloud auth print-identity-token --audiences sigstore --impersonate-service-account <name>@<project-id>.iam.gserviceaccount.com --include-email`

Note that this token will be valid for approximately one hour.

### Configuring maximum QPS per user

You can configure the test to set a maximum QPS per user. This will limit each Locust user to the specified QPS. Without this, Locust will generate an unbounded amount of traffic. You can choose to remove `wait_time` if you want this behavior, but be careful to not overwhelm a production instance.

### Running test

From within the directory with `locustfile.py`, run the command `locust`. Open `localhost:8089` in a browser. Note you can also run `locust` from the command line, see the [documentation](http://docs.locust.io/en/stable/configuration.html#configuration).

From the browser, set the following:
* Number of users. Each will run at a maximum QPS based on maximum QPS set below.
* Spawn rate, how often users are created per second
* Host, either `localhost:port` if you're running a local instance of Fulcio or `https://v1.fulcio.sigstore.dev`
* Token - The identity token from `gcloud auth`
* Max QPS per user

Click 'Start Swarming', and monitor for errors.

## Results (12/14/21)

https://github.com/sigstore/fulcio/issues/193#issuecomment-994247492
43 changes: 43 additions & 0 deletions tools/loadtest/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import base64

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from locust import HttpUser, task, constant_throughput, events
import jwt

@events.init_command_line_parser.add_listener
def _(parser):
parser.add_argument("--token", type=str, env_var="LOCUST_OIDC_TOKEN", default="", help="OIDC token for authentication with Fulcio")
parser.add_argument("--max-qps-per-user", type=float, env_var="LOCUST_MAX_QPS_PER_USER", default=1.0, help="Maximum QPS per user")

class FulcioUser(HttpUser):
# FulcioUser represents an instance of a user making requests.

# Maximum number of requests per second per user. For example, to reach 25 QPS,
# run Locust with 25 users with a constant throughput of 1.
def wait_time(self):
return constant_throughput(self.environment.parsed_options.max_qps_per_user)(self)

@task
def create_cert(self):
# create_cert generates a keypair and makes a request to Fulcio to fetch a certificate.

# Static ID token. This avoids hitting the OIDC provider with each request to fetch a new token.
token = self.environment.parsed_options.token

# Generate keypair for challenge.
privkey = ec.generate_private_key(ec.SECP256R1)
pubkey = privkey.public_key()
pubbytes = pubkey.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
content = base64.b64encode(pubbytes).decode("utf-8")

# Fetch identity of token and sign.
email = jwt.decode(token, options={"verify_signature":False})['email']
data = email.encode()
signature = privkey.sign(data, ec.ECDSA(hashes.SHA256()))
challenge = base64.b64encode(signature).decode("utf-8")

json = {"publicKey": {"content": content,"algorithm":"ecdsa"},"signedEmailAddress":challenge}
response = self.client.post("/api/v1/signingCert", json=json, headers={"Authorization": f"Bearer {token}", "Content-Type":"application/json"})
print("Response status code:", response.status_code)
3 changes: 3 additions & 0 deletions tools/loadtest/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cryptography
locust
pyjwt

0 comments on commit 5cdd33f

Please sign in to comment.