> OctoPerf Enterprise Edition - Advanced Configuration and settings

Source: https://api.octoperf.com/doc/on-premise-infra/configuration/

# Advanced Configuration

The following documentation provides guidelines to configure various aspects of OctoPerf On Premise Infra, like:

- **System Settings**: setup docker behind a proxy, Elasticsearch system configuration etc.,
- **Mailing**: support resetting account password via email,
- **Storage**: store resources (recorded requests/responses and more) either on local disk or Amazon S3,
- **High Availability**: HA is supported via built-in [Hazelcast](https://hazelcast.org/) clustering.

OctoPerf EE is a [Spring Boot](https://projects.spring.io/spring-boot/) application which uses [YML configuration files](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html).

### Docker Proxy Settings

Docker can run from behind a proxy if required. The procedure is detailed in [Docker Documentation.](https://docs.docker.com/network/proxy/)

## Elasticsearch

[Elasticsearch Database](https://elastic.co) requires the systemctl `vm.max_map_count` to be set at least to `262144`:

```bash
sysctl -w vm.max_map_count=262144
```

**To set this value permanently**, update the `vm.max_map_count` setting in `/etc/sysctl.conf`. To verify after rebooting, run `sysctl vm.max_map_count`.

See [Elasticsearch VM Max Map Count](https://www.elastic.co/guide/en/elasticsearch/reference/master/vm-max-map-count.html) for more information.

## Custom Application YML

To define your own configuration settings, you must edit the `application.yml` provided in `config/` folder.

**Custom Application YML**

1. Stop OctoPerf EE with the command `make clean` (data is preserved)
2. Edit `config/application.yml` file provided in [OctoPerf EE Setup](https://api.octoperf.com/doc/on-premise-infra/#quick-setup),
3. Start OctoPerf EE with the command `make`.

The configuration defined in your own *application.yml* takes precedence over the default built-in configuration.

!!! info
    Please refer to [Spring boot documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html) for general settings. (such as file upload limit)

!!! warning
    The `application.yml` file must be indented with `2` spaces for each level.

## Environment Variables

When you need to simply override a single setting, defining an environment variable when launching the OctoPerf EE container is the good way to go.

**How to define an environment variable**

1. Edit **docker-compose.yml** provided in [OctoPerf EE Setup](https://api.octoperf.com/doc/on-premise-infra/#quick-setup),
2. locate `environment:` section and add relevant environment variables,
3. Restart the application using **docker compose**.

Environment variables take precedence over settings defined in YML configuration files.

## Custom Settings

### Advertised Server

The backend advertises itself using `server` settings:

```yaml
server:
  scheme: https
  private-root-certificate: false # true = support SSL Certificates signed by self / private Root Certificates
  port: 8090 # internal server port
  hostname: api.octoperf.com
  public:
    port: 443 # publicly exposed port
```

The server port is configurable via:

- `server.port`: it defines the internal server port running inside the `enterprise-edition` container,
- `server.public.port`: it defines the port advertised to agents and JMeter containers. If not defined, it falls back to `server.port` setting.

!!!info
    `server.public.port` setting is useful when the backend is behind a load-balancer. Typically load balancers run on port `80` or `443`. For security reasons, the internal server cannot run port `<= 1024`. That's why we have both settings.

This must be configured to be accessible from all load generators. The JMeter containers launched on agent hosts by OctoPerf EE needs to be able to access to the backend through this hostname. The configuration above shows our Saas server configuration.

!!!warning
    Never use *localhost* because it designates the container itself. This hostname must be accessible from the agents/users perspective.

### Server Port

As the backend is behind Traefik by default, changing the default port (which is `80`) requires a few steps. Let's say you want to run the server on port `443`:

- In `traefik/traefik.yml`, locate `address: ":80"` and change it to `":443"`,
- In `docker-compose.yml`, locate `80:80` and replace by `443:443`,
- Set `server.public.port` environment setting to `443`,
- Stop and destroy containers by running `docker compose down`,
- Restart containers by running `docker compose up -d`.

The Traefik frontend port must always be the same as backend server port because the backend advertises this port to docker agents (to communicate).

!!!warning
    After changing the port, you will have to reinstall all agents, otherwise they will fail to connect to the backend.

### Internal Proxies

When the backend runs inside a Kubernetes cluster behind an ingress controller, you need to specify `server.tomcat.remoteip.internal-proxies` with the IPs of both internal ingress controllers (usually private ips like `10.42.xx.xx`) and your ingress server public IPs.

Without this configuration, the remote IP of the users and agents performing requests to the backend server are incorrectly detected.

Here is an example configuration:

```yaml
server:
  shutdown: graceful # Finish Handling current requests before shutting down
  forward-headers-strategy: native # Use X-Forwarded-For HTTP header to resolve remote IPs
  tomcat:
    remoteip:
      internal-proxies: "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" # Regexp: Ignore internal IPs like 10.xx.xx.xx, resolve remote IP instead
```

For an exhaustive list of available properties, see [Common Application Properties](https://docs.spring.io/spring-boot/appendix/application-properties/index.html).

### Elasticsearch

#### General Settings

The backend connects to Elasticsearch database using `elasticsearch.hostname` setting:

```yaml
elasticsearch:
  hostname: elasticsearch
  username: elastic # default username is "elastic"
  password: # empty by default; if none provided, no authentication is used
  client:
    buffer-limit: 104857600 # 100Mb by default
  indices:
    prefix: octoperf_
  scroll:
    size: 256
    keep-alive-min: 15
```

Elasticsearch can be configured with:

- **hostname**: This must be configured to an IP or a hostname accessible from the OctoPerf EE container. **Multiple hosts can be defined separated by comas**, 
- **username**: username to authenticate with Elasticsearch if [minimal security is enabled](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html). `elastic` is the default username,
- **password**: password to authenticate with Elasticsearch. If empty, no authentication is used,
- **client**:

    - **buffer-limit**: maximum response size the client can handle,

- **indices**:

    - **prefix**: `octoperf_` by default, indices are created with `octoperf_` prefix. Example: `octoperf_user` indice contains the user accounts.
    - **alias**: none by default. Indices can be created with the given `alias`. This setting is useful to point to use [aliases](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html) to upgrade a database with no service interruption. Make sure that existing indices already have same aliases configured before editing this setting (otherwise those settings won't be found by the backend).

- **scroll**:
    - **size**: scroll window size, `256` elements by default,
    - **keep-alive-min**: scroll keep alive timeout between each window, `15` by default. In minutes.

!!! info
    Never use *localhost* hostname. Use the IP address of the machine hosting your database, even if it's on the same machine.

#### Snapshots Settings

Snapshots are the equivalent of an **incremental backup** of all the database indices. The backend can trigger an [elasticsearch snapshot](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html) creation every night with the configuration below:

```yaml
elasticsearch:
  snapshots:
    enabled: true 
    repository: nfs-server # name of the repository to backup
    keep-last: 7 # 7 days rolling window
```

!!! tip
    Follow this [blog post to store ElasticSearch backups on a shared NFS](https://octoperf.com/blog/2019/05/02/elasticsearch-snapshot-backup-shared-nfs/).

### Container Orchestrator

Both `docker` (by default) and `podman` are supported:

```yaml
octoperf:
  agent:
    orchestrator: docker # or podman
```

It affects the command-line being generated to start the agent.

### SMTP Server

an SMTP mail server can be specified in order to support user account password recovery:

```yaml
spring:
  mail:
    enabled: true
    host: smtp.mycompany.com
    port: 587
    username: username@mycompany.com
    password: passw0rd
    properties:
      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.starttls.required: true
    from:
      name: MyCompany Support
      email: from@mycompany.com
```

For a complete list of supported `properties`, see [Javax Mail Documentation](https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html).

### High Availability

Prerequisites:

* You need to have at least 3 Hosts in a cluster with networking enabled between them.

To enable high-availability, the different backend servers need to form a cluster. Each backend server must be able to communicate with others. To enable HA using Hazelcast, define the following configuration:

```yaml
clustering:
  driver: hazelcast
  members: enterprise-edition
  quorum: 2
```

It takes the following configuration:

- **driver**: either `noop` (no clustering), `hazelcast` (using [Hazelcast](https://hazelcast.org)) or `ignite` (using [Apache Ignite](https://ignite.apache.org)),
- **members**: comma separated list of hostnames or IPs of the members of the cluster,
- **quorum**: minimum number of nodes necessary to operate. `quorum = (n+1) / 2`, where `n` is the total number of nodes in the cluster.

### 3 Containers Setup

Let's describe what's being configured here:

- **driver**: *hazelcast* indicates to use Hazelcast clustering, Hazelcast is a Distributed Java Framework we use internally to achieve HA,
- **hazelcast.members**: hostnames of all the backends separated by comas,
- **hazelcast.quorum**: The quorum indicates the minimum number of machines within the cluster to be operational. *quorum = (n + 1) / 2*, where n is the number of members within the cluster.

**hazelcast.members** can be either a DNS hostname pointing to multiple IPs or multiple IPs **separated by comas**.

### Resources Storage

Resources like recorded requests, recorded responses or files attached to a project are stored inside the container in folder `/home/octoperf/data`:

```yaml
storage:
  quota:
    max-file-count: 1024 # max number of files per project / result
    max-total-size: 1047741824 # max total bytes per project / result
  driver: fs
  fs:
    folder: data
```
*Folder location is resolved inside the Docker container*

When no [Docker volume mapping](https://docs.docker.com/engine/tutorials/dockervolumes/) is configured, the data is lost when the container is destroyed. In order to avoid this, you need to setup a volume mapping like for the configuration. **Example:** `/home/ubuntu/docker/enterprise-edition/data:/home/octoperf/data`

When running the backend in HA, it's not possible to store data on the local disk. The request being sent to get resources may hit any backend server, but the wanted resource may be stored on another server. In this case, it's better to store resources on a global service shared by all backends like [Amazon S3](https://aws.amazon.com/s3/).

**Configuring Amazon S3 Storage**

```yaml
storage:
  driver: s3
  s3:
    region: eu-west-1
    bucket: my-bucket
    access-key: 
    secret-key: 
```

Let's describe what is being configured here:

- **driver**: Amazon S3 driver,
- **s3**:

    - **region**: specifies the region where the target S3 bucket is located,
    - **bucket**: S3 bucket name, usually my-bucket.mycompany.com,
    - **access-key**: an AWS access key with access granted to the given bucket,
    - **secret-key**: an AWS secret key associated to the access key.

We suggest to setup an Amazon User with [permission granted only to the target S3 bucket](http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-access-control.html). Here is an example Policy:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1427454857000",
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*",
                "s3:Put*",
                "s3:Delete*"
            ],
            "Resource": [
                "arn:aws:s3:::bucket.mycompany.com",
                "arn:aws:s3:::bucket.mycompany.com/*"
            ]
        }
    ]
}
```

### User Management

User management can be customised using the following configuration:

```yaml
users:
  administrators: admin@octoperf.com # coma separated list of usernames
  registration:
    enabled: true # default: true. Are new users allowed to create an account?
    welcome-email:
      enabled: true # default: true (available in version >= 15.0.1)
    email-activation:
      enabled: false # default: false. Should users receive an email to activate their account?
    disposable-emails:
      block-known-domains: true # default: false.
      block-known-mail-servers: true # default: false (available in version >= 18.0.0)
      dns-server: dns://10.0.0.2 # default: the DNS servers of the machine (available in version >= 18.0.0)
      refresh-known-domains: true # default: false (available in version >= 18.0.1)
      known-domains-timeout: PT30S # default: PT30S (available in version >= 18.0.1)
      custom-domains: mydomain.com # default: none. custom user-defined email domains to block
  password-recovery:
    enabled: true # default: true. Can users recover their account password by email?
  explicit-login:
    enabled: true # default: true. Can users login using the regular login form?
```

Email activation requires a valid email configuration (otherwise users won't receive the activation email).

`block-known-domains` refuses a registration whose email domain is in the list of disposable email domains shipped with OctoPerf. Disposable email services keep creating new domains, which reach the list only later. `block-known-mail-servers` also refuses a domain whose emails are received by the mail servers of a listed domain, so that a new domain of a known service is refused as well. Each setting works without the other.

With `block-known-mail-servers`, OctoPerf asks a DNS server for the mail servers of each domain that is not already refused, and waits at most 2 seconds for the answer. When the DNS server cannot answer, the registration is accepted: a DNS outage never blocks a registration. `dns-server` sets the DNS server to ask, as `dns://host` or `dns://host:port`. A malformed value prevents the server from starting.

`refresh-known-domains` keeps the list of disposable email domains up to date between two versions of OctoPerf. Each server downloads the public list of disposable email domains when it starts, then once a day, and both `block-known-domains` and `block-known-mail-servers` use it. A new disposable domain is refused within a day, without waiting for a new version of OctoPerf.

The downloaded list adds to the list shipped with OctoPerf: a domain in either list is refused, and a download never removes a domain from the shipped list. When a download fails, the server keeps the previous one. `known-domains-timeout` sets how long a download may last, as an ISO-8601 duration (`PT30S` is 30 seconds). With `refresh-known-domains` off, the server downloads nothing and uses the shipped list only. The `custom-domains` are always refused.

Behind a proxy, add the proxy to the `JAVA_OPTS` of the `enterprise-edition` service in `docker-compose.yml`, for instance `-Dhttps.proxyHost=proxy.mycompany.com -Dhttps.proxyPort=3128` ([an example of `JAVA_OPTS`](https://api.octoperf.com/doc/on-premise-infra/troubleshooting/#out-of-memory)).

An activation link can only be used once, and stays valid for at most 1 day. A link sent before a full restart of the server (every node of the cluster) no longer works: the user can ask for a new activation email from the page the link opens.

### Rate Limits

Rate limits protect your server against repeated calls, such as password guessing or scripts creating projects in a loop. Each limit allows at most `capacity` calls per `duration`, counted separately for each user, email address or IP address depending on the limit, and for each action the limit applies to. Calls are given back progressively: one call becomes available again every `duration / capacity`.

```yaml
octoperf:
  rate-limit:
    users:
      login:
        duration: 10m
        capacity: 10
    workspaces:
      create:
        duration: 1m
        capacity: 5
```

The following limits are available:

| Limit | What it limits | Counted per | Default in Enterprise Edition |
|-------|----------------|-------------|-------------------------------|
| `users.login` | Failed attempts to log in (password or second factor), reset a password or resend an activation email, counted together | IP address | `10` per `10m` |
| `users.account-mails` | Account emails: activation, password reset, "account already exists", each kind counted on its own | Email address | Not limited |
| `notifications.trial` | Sending a test notification | User | Not limited |
| `workspaces.create` | Creating a workspace | User | Not limited |
| `projects.create` | Creating a project | User | Not limited |

A limit you do not configure does not limit anything. Only `users.login` is configured by default: after `10` failed attempts from the same IP address, this address is blocked and gets back one attempt per minute. Failures on login, two-factor login, forgot password, reset password and resend activation add up, and a blocked address is refused on all of them. A failure is a wrong password or second-factor code, a password reset email requested for an address without an account, an activation email requested for an address without an account awaiting activation, or an invalid or expired password reset link: successful requests do not count. A successful login does not reset the count. A blocked attempt is refused with an HTTP `409` response and the message `IP Banned, Too Many Attempts!`.

Beyond any other limit, the call is refused with an HTTP `429` response, whose `Retry-After` header gives the waiting time. There is one exception: beyond the `users.account-mails` limit, the "account already exists" email is silently not sent, so that the response does not reveal whether an account exists for this address.

Each setting must be valid, otherwise the server does not start:

- **duration**: a whole number of seconds, written with its unit, for example `5s`, `1m` or `10m`,
- **capacity**: `1` or more.

!!! info
    You only need to write the limits you want to change: the other ones keep their default value. Counters are kept in memory by each backend node: they start over when the node restarts, and in a [cluster](#high-availability) each node counts the calls it receives on its own.

### Quotas

Quotas cap the number of workspaces and projects your users can create. No quota is configured by default: everything is unlimited.

```yaml
octoperf:
  quotas:
    workspaces-per-user: 20
    projects-per-workspace: 50
```

- **workspaces-per-user**: the number of workspaces where an account has the [Admin role](https://api.octoperf.com/doc/workspace/), whether it created them or was added to them as an administrator. Workspaces where the account is a Tester, Test Executor or Viewer do not count. It is checked when a workspace is created.
- **projects-per-workspace**: the number of projects in a workspace. The copies of projects kept with test results do not count. It is checked when a project is created, and when a project is moved to another workspace.

The **Default** workspace and its **Sample Project**, created when a user signs up, count like any other. For example, with `workspaces-per-user: 1`, a user cannot create any workspace, and with `projects-per-workspace: 1`, a user cannot create a project in their Default workspace while the sample project is there.

Once a quota is reached, the action is refused with an HTTP `409` response and a message such as `Quota reached: 20 workspaces per account` or `Quota reached: 50 projects per workspace`. Existing workspaces and projects are kept: only new ones are refused.

A quota must be `1` or more and name one of the two quotas above, otherwise the server does not start. For example, a misspelled quota name stops the startup with a message such as `octoperf.quotas.unknown-per-thing: no quota counter counts it`.

### Data Eviction

Unnecessary data like old results or orphaned files can be automatically deleted using the following settings:


```yaml
octoperf:
  eviction:
    audit-logs:
      enabled: true # false by default
      months: 24
    test-results:
      enabled: true # false by default
      months: 24
    orphaned-project-blob: # orphaned project files
      enabled: true # false by default
```

By default, evicting data is disabled.

### Runtime settings

Several runtime settings can be configured:


```yaml
octoperf:
  runtime:
    abort-stale-test:
      timeout-min: 20 # Duration in min before any unresponsive test is killed
    check-vu:
      max-iterations: 5 # Maximum number of iterations allowed for a check VU
```

### Single Sign-On

#### OpenID Connect (Oauth2)

OctoPerf EE supports Oauth2 / OpenID Connect authentication through [Spring Boot properties](https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2login-boot-property-mappings). Here is an example `application.yml`:

```yaml
spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-name: okta
            client-secret: okta-client-secret
            allowed-domains: "mydomain.com, myotherdomain.com" # (Optional) restrict domains allowed to login. If empty, all are accepted
            username-suffix: "" # (Optional) Example: @mydomain.com if the username is not a valid email.
            name: "Okta" # (Optional)
            description: "Login With Okta" # (Optional)
            scope: openid,email # oauth2 scopes
            client-authentication-method: client_secret_basic
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}" # DO NOT MODIFY THIS SETTING
        provider:
          okta:
            authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
            token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
            user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
            jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
            user-name-attribute: sub
```

The Oauth2 provider (here `okta`) must then be declared in ui configuration `config.json` to enable Oauth2 login. The `user-name-attribute` setting must match the username of the user logging in. `user-name-attribute` must point to an **email attribute**. If not, please also define `username-suffix` property.

**Only emails are allowed as usernames**. In that case, when the user `jsmith` logs in, it creates an account with username `jsmith@mydomain.com`.

!!! info
    In order for the login page to be compatible with those changes, you must also [edit the config.json file](#ui-oauth-login-configuration).

#### Microsoft Azure


```yaml
spring:
  security:
    oauth2:
      client:
        registration:
          azure:
            provider: azure
            client-name: Azure
            client-id: ${CLIENT_ID} # CLIENT_ID environment variable
            client-secret: ${CLIENT_SECRET} # CLIENT_SECRET environment variable
            scope: openid, email, profile
        provider:
          azure:
            issuer-uri: https://login.microsoftonline.com/${TENANT_ID}/v2.0 # TENANT_ID environment variable
            user-name-attribute: email
```

When using Microsoft Azure authentication with OpenID Connect, it's important to use at least `v2.0` API version endpoints. As stated in [their notes and caveats](https://docs.microsoft.com/en-us/azure/active-directory/develop/userinfo#notes-and-caveats-on-the-userinfo-endpoint):

> We recommend that all OIDC compliant apps and libraries use the v2.0 endpoint to ensure compatibility.

This is needed otherwise you may experience issues retrieving the account email and associating it with an OctoPerf account.


If you plan to configure Azure SSO through the integrated [SSO Administration Panel](https://api.octoperf.com/doc/administration/#sso), we suggest to use the following settings.

**SSO Registration**:

- `Client ID`: your client id,
- `Client Secret`: your client secret,
- `Client Name`: same as the SSO id, in lowercase without any space,
- `Auth Method`: `Client Secret Basic`,
- `Auth Grant Type`: `Authorization Code`,
- `Scopes`: `openid,email,profile`,
- `Allowed domains`: email domain name of users registering. Example: mycompany.com,
- `Username suffix`: appends `@mycompany.com` if configured with `mycompany.com` (in case the username attribute is not a valid email),
- `Redirect URI`: `{baseUrl}/{action}/oauth2/code/{registrationId}`.


**SSO Provider**: (replace `TENANT_ID` by your own tenant id provided by Microsoft Azure)

- `Authorization URI`: `https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize`,
- `Token URI`: `https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token`,
- `User Info URI`: `` (leave empty),
- `JWK Set URI`: `https://login.microsoftonline.com/TENANT_ID/discovery/v2.0/keys`,
- `Issuer URI`: `https://login.microsoftonline.com/TENANT_ID/v2.0`,
- `Username Attribute`: `preferred_username` (SSO attribute which maps the user email).


The redirect URI to register on SSO server is: `http(s)://HOSTNAME/login/oauth2/code/SSO_ID`, where:

- `http(s)://HOSTNAME`: http scheme and hostname where octoperf server is running. Example: `https://api.octoperf.com`,
- `SSO_ID`: the SSO registration ID. Example: `azure` (it's NOT the Client ID).

!!! info
    In order for the login page to be compatible with those changes, you must also [edit the UI configuration file](#ui-oauth-login-configuration).

#### LDAP

By default, OctoPerf EE creates and manages users inside its own database.

OctoPerf EE supports seamsless integration with a third party authentication server (**Single Sign On**, aka SSO) based on the [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) protocol.

!!! info
    It's strongly recommended to be assisted by your **System Administrator** when configuring OctoPerf to support LDAP authentication.

**Configuring LDAP Authentication**

```yaml
users:
  driver: ldap
  ldap:
    # ldap or ldaps
    protocol: ldap
    hostname: localhost
    port: 389
    base: dc=example,dc=com
    principal-suffix: "@ldap.forumsys.com"

    authentication:
      # anonymous, simple, default-tls, digest-md5 or external-tls
      method: simple
      username: cn=read-only-admin,dc=example,dc=com
      password: password

    # Cache Ldap users
    cache:
      enabled: true
      durationSec: 300

    # user password encryption
    # OpenLDAP: leave as plain (even if encoded within Ldap)
    password:
      type: plain
      # sha, ssha, sha-256, md5 etc.
      algorithm: sha

    # LDAP Connection pooling
    pooled: false

    # User mapping
    object-class: person
    attributes:
      id: uid
      name: cn
      mail: mail
    
    # Ignorable errors
    ignore:
      partial-result: false
      name-not-found: false
      size-limit-exceeded: true
      
    # Filters that will be added to the LDAP query as "key=value AND key2=value2"
    query:
      filters:
        memberOf: CN=groupname,OU=ApplicationGroupes,OU=Users,DC=domain,DC=com
```

!!! info
    The configuration above shows the default configuration used by the OctoPerf server when **ldap** driver is configured.

The LDAP authentication supports a variety of settings:

- **protocol**: either *ldap* or *ldaps*,
- **hostname**: hostname of the LDAP server,
- **port**: *389* by default, LDAP server connection port,
- **base**: an LDAP query defines the directory tree starting point,
- **principal-suffix**: Empty by default. Example: `@ldap.forumsys.com`. Suffix which completes the User name to authenticate with the LDAP server,

- **authentication**:

    - **method**: (authentication method to use. Can be `anonymous`, `simple`, `default-tls`, `digest-md5` or `external-tls`. The last one requires [additional JVM configuration](https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ldap.html).
    - **username**: depending on the authentication method. Must be a valid Ldap Distinguished Name (DN),
    - **password**: depending on the authentication method. Password associated to the given username,

- **cache**:

    - **enabled**: `true` by default. Enables User cache to reduce the load on the LDAP server,
    - **durationSec**: `300` sec by default. Any change on the LDAP server takes up to this duration to be taken into account,

- **password**: Defines how the user password should be encrypted,

    - **type**: `plain` by default. Set `hash` and refer to `algorithm` to set the right hashing algorithm,
    - **algorithm**: `sha` by default, required if `type` is set to `hash`. Supports: `sha`, `sha-256`, `sha-384`, `sha-512`, `ssha`, `ssha-256`, `ssha-384`, `ssha-512`, `md5`, `smd5`, `crypt`, `crypt-md5`, `crypt-sha-256`, `crpyt-sha-512`, `crypt-bcrypt` and `pkcs5s2`.

- **pooled**: whenever to use a connection pool to connect to the LDAP server. Pooling speeds up the connection,
- **object-class**: *objectClass* defines the class attribute of the users. Users are filtered based on this value,

- **attributes**:
    - **id**: `required`. Maps the given attribute (*uid* by default) to the user id,
    - **name**: `optional`. Maps the given attribute (*cn* by default) to the user firstname and lastname,
    - **mail**: `optional`. Maps the given attribute (*mail* by default) to the user mail. If empty, it uses the `id` concatenated with the `principal-suffix`.

- **ignore**:

    - **partial-result**: `optional`. Set to `true` to ignore `PartialResultException`. AD servers typically have a problem with referrals. Normally a referral should be followed automatically, but this does not seem to work with AD servers. The problem manifests itself with a `PartialResultException` being thrown when a referral is encountered by the server. Setting this property to `true` presents a workaround to this problem,
    - **name-not-found**: Specify whether `NameNotFoundException` should be ignored in searches. In previous version, `NameNotFoundException` caused by the search base not being found was silently ignored,
    - **size-limit-exceeded**: Specify whether `SizeLimitExceededException` should be ignored in searches. This is typically what you want if you specify count limit in your search controls.

!!! warning
    Choosing between **Internal** and **LDAP** authentication must be done as soon as possible. Existing internal users won't match LDAP ones if changed later, even if they share the same username.

    When using **LDAP authentication**, a few features related to user management are disabled or non-functional:
    
      - changing or resetting a user password,
      - editing user profile information,
      - and registering a new user through the registration form or the API.

!!! note

    You should configure the UI `config.json` file using [LDAP login settings](#ui-ldap-login-configuration).

### Agent Token Secret

The tokens of the [on-premise agents](https://api.octoperf.com/doc/on-premise-agent/provider-type/on-premise/) are signed with a secret generated once per installation. At its first start, the server generates a random secret, stores it encrypted in the `configentry` index of the database (`octoperf_configentry` with the default [indices prefix](#general-settings)), and every backend node reads it from there at startup.

#### Rotate the secret

Rotating the secret invalidates the token of every on-premise agent:

1. Stop every backend node. Each node reads the secret at startup, so a node still running would keep signing with the former one.
2. Delete the stored secret from the database:

    ```bash
    curl -X DELETE "http://elasticsearch:9200/octoperf_configentry/_doc/jwt-secret"
    ```

    Replace `elasticsearch:9200` with your Elasticsearch address, add `-u elastic:<password>` if authentication is enabled, and adapt `octoperf_` to your indices prefix.

3. Start the backend nodes: the first one to start generates a new random secret, and the others read it.
4. [Remove and recreate](https://api.octoperf.com/doc/on-premise-agent/provider-type/on-premise/#remove-and-recreate-the-agent) every on-premise agent with a new installation command, taken from the **Add Agent** button of the providers list.

### MCP Server / OAuth Identity Provider

OctoPerf Enterprise Edition can act as an **OAuth 2.1 / OpenID Connect Identity Provider** so that third-party applications (typically [Claude.ai Custom Connectors](https://claude.com/connectors) talking to the OctoPerf MCP server) sign users in with their existing OctoPerf credentials and obtain short-lived JWT access tokens. The IdP is bundled into the OctoPerf EE backend; no separate process to deploy.

The IdP issuer (`iss`), the default `aud` it stamps on JWTs, and the SPA login redirect are all **derived from the backend's public origin** — the `server.scheme` / `server.hostname` / `server.public.port` you already set in [Advertised Server](#advertised-server). There is nothing IdP-specific to bind per environment; just make sure the public origin is correct:

```yaml
server:
  scheme: https
  hostname: YOUR-OCTOPERF-HOST   # the host clients reach through the reverse proxy
  public:
    port: 443
```

This yields:

- `iss` = the backend public origin (e.g. `https://YOUR-OCTOPERF-HOST`),
- default `aud` = `<origin>/mcp` (stamped on JWTs minted without an RFC 8707 `resource` parameter),
- SPA login redirect = `<origin>/ui/access/login` (frontend context path `/ui` + `/access/login`).

!!! note "How the `aud` claim is resolved"
    OAuth tokens carry an `aud` claim identifying the intended *resource*. Claude.ai alternates three URL forms for the same logical resource across the OAuth flow: the bare origin, the origin with a trailing slash, and `<origin>/mcp`.

    - When a client sends an RFC 8707 `resource` parameter, the IdP stamps that value.
    - Otherwise the IdP stamps the default `<origin>/mcp`.
    - The MCP server accepts **all three forms** derived from the same public origin, so every variant validates.

The IdP exposes:

- `GET /.well-known/oauth-authorization-server` and `/.well-known/openid-configuration` — discovery
- `GET /auth/oauth2/authorize` — authorization endpoint (redirects to the SPA login)
- `POST /auth/oauth2/token` — code/refresh exchange
- `POST /auth/oauth2/revoke` — RFC 7009 revocation
- `GET /auth/oauth2/jwks` — JWK set
- `POST /auth/connect/register` — RFC 7591 anonymous Dynamic Client Registration

#### JWK persistence (RSA signing key)

The IdP signs JWTs with an RSA-2048 key. By default, the key is **regenerated at every OctoPerf EE container start**, which means every JWT issued by the previous container instance is invalidated on the next restart. This is fine for evaluation but unsuitable for production.

To persist the signing key across restarts, generate a `PKCS#8` PEM keypair and feed it to OctoPerf via an environment variable or a YAML property:

```bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out auth-jwk.pem
```

**Option 1 - environment variable (recommended for Docker / docker-compose):**

```yaml
environment:
  - OCTOPERF_AUTH_JWK_PRIVATE_KEY=$(cat auth-jwk.pem)   # multiline PEM — most shells preserve newlines
```

**Option 2 - `application.yml`** (works in any deployment but exposes the key to anyone with file access):

```yaml
octoperf:
  auth:
    jwk:
      private-key: |
        -----BEGIN PRIVATE KEY-----
        MIIEv...
        -----END PRIVATE KEY-----
```

**Option 3 - Kubernetes Secret:**

```bash
kubectl -n <namespace> create secret generic auth-jwk --from-file=private-key=auth-jwk.pem
```

Reference it from the StatefulSet/Deployment env:

```yaml
env:
  - name: OCTOPERF_AUTH_JWK_PRIVATE_KEY
    valueFrom:
      secretKeyRef:
        name: auth-jwk
        key: private-key
        optional: true     # pod still boots if the Secret is absent (falls back to ephemeral)
```

The key id (`kid` in JWT headers and JWKS responses) is the [RFC 7638 thumbprint](https://datatracker.ietf.org/doc/html/rfc7638) of the public key - stable across restarts as long as the key bytes don't change, so MCP clients can cache JWKS without staleness.

#### Graceful key rotation

To rotate the signing key **without invalidating live JWTs**, run the rotation in two phases separated by one access-token TTL (5 min by default):

1. Keep the OLD public key advertised on JWKS during the grace window:

    ```yaml
    octoperf:
      auth:
        jwk:
          private-key: <NEW PKCS#8 PEM>
          previous-public-keys:
            - |
              -----BEGIN PUBLIC KEY-----
              <OLD X.509 SubjectPublicKeyInfo PEM>
              -----END PUBLIC KEY-----
    ```

    Restart. JWTs minted before the rotation still verify against the old public key (now in `previous-public-keys`); new JWTs are signed with the new key.

2. After 5 minutes (= `accessTokenTimeToLive`), every old access JWT has expired. Drop the `previous-public-keys` entry and restart again - JWKS now advertises only the new key.

Refresh tokens are opaque server-side state and survive rotation unchanged - they get exchanged for new JWTs signed with the active key. **Nuclear option** (incident response): rotate `octoperf.auth.jwk.private-key` *without* listing the old key under `previous-public-keys`. Every live access JWT becomes invalid on the next restart.

#### Deploying the MCP server

The MCP server itself is a separate Spring Boot service shipped as the `octoperf/mcp-server` Docker image and configured via its own `application.yml`. See [MCP Server](https://api.octoperf.com/doc/on-premise-infra/mcp-server/) for the deployment recipe (docker-compose service, Traefik routing, standalone-docker recipes, verifying the deployment, Claude.ai sandbox allowlist).

Make sure to also expose the per-user **Connected Apps** management page through the UI by setting `connectedApps: true` in `config.json` (see [UI configuration](#ui-configuration) below).

### Virtual User Validation Storage

[Virtual user validation](https://api.octoperf.com/doc/design/edit-virtual-user/validation/) http requests and responses are stored within Elasticsearch by default.

```yaml

validation:
  driver: elasticsearch # or blob, which means on disk
```

Validation http requests can either be stored in blob storage (with `validation.driver: blob`; see *Resources Storage* section), or within the database (with `validation.driver: elasticsearch`).

### Recorded and Validation Data Retention

A daily task deletes the http requests and responses kept for [Virtual User validations](https://api.octoperf.com/doc/design/edit-virtual-user/validation/) and for recordings once they are older than their retention:

```yaml
octoperf:
  validation:
    retention-days: 7 # days the requests and responses of a validation are kept, counted from the validation
  recorded:
    retention-days: 730 # days recorded requests and responses are kept, counted from their import
```

The recorded requests and responses of a Virtual User are also deleted with the Virtual User or its project.

### Errors storage

The [Error table](https://api.octoperf.com/doc/analysis/edit-bench-report/report-items/errors-table/) keeps track of only two errors of each kind. It also has some max limits per JMeter container and test result that can be configured:

```yaml
octoperf:
  runtime:
    errors:
      max-per-test: 500 # max errors per test result,
      max-per-instance: 100 # max errors per jmeter container / instance,
      max-per-request: 2 # max identical errors/code per request.
```

!!! warning
    Increasing these values can have adverse effect on the storage size required. It can also prevent your reports to load if you try to display too many errors per report.

### Nightly Cleanup Batches

Some internal batch tasks (disabled by default) can be run every night to reclaim unnecessary disk space being used. Those batch jobs can be enabled using the following properties:

```yaml
# Available in version >= 12.0.3
octoperf:
  eviction:
    # scans results to keeps only the first 500 error requests / responses
    bench-error-blob:
      enabled: false
    # scans virtual users modified within last 2 days to removed orphaned recorded requests / responses
    orphaned-http-recorded:
      enabled: false
    # scans projects storage folder and removes them if the project doesn't exist anymore
    orphaned-project-blob:
      enabled: false
    # scans results storage folder and removes them if the result doesn't exist anymore
    orphaned-result-blob:
      enabled: false
```

When upgrading from version `12.0.2` and below, we suggest to temporarly enable those batches to clean previously undeleted files. Those batches run during the night.

### reCAPTCHA

[Google's reCAPTCHA](https://www.google.com/recaptcha/intro/v3.html) can be used to prevents bots from creating accounts on your OctoPerf platform.

```yaml
recaptcha:
  enabled: true
  endpoint: https://www.google.com
  private.key: your-private-key
```

It takes the following configuration:

- **enabled**: `true` to activate Recaptcha,
- **endpoint**: `https://www.google.com` to use Google's recaptcha service,
- **private.key**: your reCAPTCHA private key (obtained in the Google's reCAPTCHA admin console).

!!! warning
    **The public reCAPTCHA key must be configured in the `config.json` file of the frontend**, using the json key `"captcha": "you-public-key"`.
    Please have a look at the [GUI / Frontend configuration section](#ui-configuration) to know how to map the configuration file.

### JMeter Plugins

By default, JMeter plugins are not downloaded. This feature can be configured as following:

```yaml
jmeter:
  plugins:
    download-from: NONE
```

JMeter plugins are downloaded from:

- `SERVER`: OctoPerf server downloads them when importing the JMX. The plugin files are available within project files,
- `JMETER`: [PluginsManagerCMD](https://jmeter-plugins.org/wiki/PluginsManagerAutomated/) is executed just before running the test with `install-for-jmx` argument, 
- `NONE`: plugins are not downloaded at all.

### K6

[K6 virtual users](https://api.octoperf.com/doc/design/create-virtual-user/import-k6-scripts/) run in the `octoperf/k6` image. These settings apply to them:

```yaml
docker:
  image:
    k6: octoperf/k6:18.0.0
octoperf:
  k6:
    extensions: octoperf,sql,sql/driver/postgres,sql/driver/mysql,redis,kafka
    custom-metrics:
      max: 100
```

- `docker.image.k6`: the image the load generators run K6 virtual users in, prefixed by the registry of the provider when it has one. Set it to an image you [rebuilt with extensions of your own](https://api.octoperf.com/doc/design/edit-virtual-user/action-types/k6-actions/k6-protocols/#adding-your-own-extensions), or pin the version of your installation.
- `octoperf.k6.extensions`: the xk6 extensions that image carries, by the name after `k6/x/`, separated by commas. The sanity check refuses a script importing an extension outside this list. Keep `octoperf` in it.
- `octoperf.k6.custom-metrics.max`: the custom metric names each load generator reports, past which a new name is dropped.

### File Upload Limit

The default file upload limit is set to `200MB`.

```yaml
spring:
  servlet.multipart:
    max-request-size: 200MB
    max-file-size: 200MB
```

This example sets the limit to `200MB`.

### Notifications

Tests can be marked as failed when the error percentage is higher than a certain value.

```yaml
octoperf:
  notifications:
    max-errors-percent: 5
```

You can configure system-wide notifications to be sent by your OctoPerf server:

```yaml
octoperf:  
  notifications:
    enabled: true
    slack:
      - channel: user-activity
        token: XXX
        eventIds:
          - CHANGE_USERNAME
          - CLOUD_HOST_ISSUE
          - JMX_IMPORT
          - TEST_ENDED
          - TEST_ERROR
          - TEST_FAILED
          - TEST_PASSED
          - TEST_STATUS
          - TEST_STARTED
          - USER_ACTIVATED
          - USER_LOGIN
          - USER_LOGOUT
          - USER_PASSWORD_CHANGED
          - USER_REGISTERED
```

### Amazon Instance Tags

Aws Instances created by OctoPerf can be tagged with the following settings:

```yaml
octoperf:
  amazon:
    tags:
      key: value
      key2: value2
```

Those tags are applied to all Amazon providers configured on the platform.

## UI configuration

The file `config.json` lets you configure the UI of OctoPerf. An example of this configuration file:

```json
{
  "version": "octoperf-1",
  "backendApiUrl": "https://api.octoperf.com",
  "docUrl": "https://api.octoperf.com/doc",
  "adminEmail": "support@octoperf.com",
  "login": {
    "@type": "form",
    "register": true,
    "activate": true,
    "forgotPassword": true
  },
  "notices": [
    {
      "id" : "release-test",
      "type" : "text",
      "visibilities" : [
        "closable"
      ],
      "position" : "bottom",
      "location" : "full-page",
      "color" : "white",
      "backgroundColor" : "info-alpha",
      "options" : {
        "text" : "**A new version of OctoPerf is out!** Check the release notes at <a href='https://api.octoperf.com/doc/release-notes/' target='blank'>https://api.octoperf.com/doc/release-notes</a>.",
        "horizontalAlign" : "left"
      }
    }
  ],
  "twitter": true,
  "cloud": true,
  "connectedApps": true,
  "recaptcha": "recaptcha-key"
}

```

This file supports a variety of settings:

- **version**: the frontend application version used to store data in the browser local storage (changing this version resets all stored local preferences for every user),
- **backendApiUrl**: the URL of the backend server, i.e. `https://api.octoperf.com` for OctoPerf SaaS,
- **docUrl**: the URL of the documentation, i.e. `https://api.octoperf.com/doc` for OctoPerf's public documentation,
- **adminEmail**: the email address the users may contact for Administrator operations such as removing a Workspace,
- **login**: see configuration [bellow](#ui-login-configuration),
- **notices**: you can add notices in the application, displayed below the notice properties.
    - **id**: a unique identifier used to distinguish each notice.
    - **type**: type of the notice. Use "text" for standard notices. _(Other values exist but are reserved for OctoPerf SaaS internal usage.)_
    - **visibilities**: defines the visibility behavior of the notice. Possible values include:
        - **closeable**: the notice can be closed by the user (a close button appears in the top-right corner).
        - **true**: default value
    - **location**: defines where the notice is displayed in the application:
        - **access**: shown on all access pages before login (e.g., login, forgot password, etc.).
        - **full-page**: shown on all pages after the user has logged in.
    - **position**: specifies where the notice appears within the page:
        - **top**: displayed at the top of the page.
        - **bottom**: displayed at the bottom of the page.
    - **color**: "white", "info", "warn" or "primary" 
    - **backgroundColor**: "info-alpha", "warn-alpha" or "primary-alpha"
    - **options**: contains the **textual content** of the notice and its alignment. (**center** or **left**) 
    <pre>
    {
      "text" : "Maintenance every monday from 7 to 8AM UTC.",
      "horizontalAlign" : "center"
    }
   </pre> 
- **twitter**: [true/false] displays a Twitter feed on the right side of the application,
- **cloud**: [true/false] activates a tab in the Administration page that lists all currently started Cloud instances,
- **connectedApps**: [true/false] exposes the **Connected Apps** sub-page under the user profile (lists active OAuth grants - typically AI agents talking to the [MCP server](https://api.octoperf.com/doc/mcp/) - and lets users revoke them). Default `false`. Turn on once the MCP server is reachable from end users; the admin-side **OAuth Clients** page is always available to administrators and is not gated by this flag,
- **recaptcha**: "reCAPTCHA_public_key" your reCAPTCHA public key, see [backend's configuration](#recaptcha).

You can map a docker volume to this file `/usr/share/nginx/html/ui/assets/config.json` to customize its content, and for example, update the administrator email address.

This must be done in the `docker-compose.yml` file. Simply edit the **enterprise-ui** service to map the file:

```yaml
  enterprise-ui:
  volumes:
    - /path/to/config.json:/usr/share/nginx/html/ui/assets/config.json
```

### UI Login configuration

#### UI Form login configuration

This is the most basic mode of login configuration used to connect to the backend server.
We assume here that the [backend manages user on its own](#user-management). 

```json
{
  "login": {
    "@type": "form",
    "register": true,
    "activate": true,
    "forgotPassword": true
  }
}
```
The supported settings are:

- **@type**: the type of login configuration, must be set to `"form"`,
- **register**: [true/false] register page availability,
- **activate**: [true/false] activate page availability,
- **forgotPassword**: [true/false] forgot password page availability.


#### UI OAuth login configuration

The page `http://FRONTEND_URL/#/access/connect/{clientId}` is automatically generated based on [the oauth2 settings defined server-side](#openid-connect-oauth2) in `application.yml`.

```json
{
  "login": {
    "@type": "oauth",
    "id": "clientId"
  }
}
```

The supported settings are:

- **@type**: the type of login configuration, must be set to `"oauth"`,
- **id**: The Oauth client id, `okta` in the [server-side configuration example](#openid-connect-oauth2).

#### UI LDAP login configuration

If using the server-side [#ldap](#ldap) configuration you can set the frontend configuration to:

```json
{
  "login": {
    "@type": "ldap"
  }
}
```

This will disable register, activate, and forgot password pages.

The supported settings is:

- **@type**: the type of login configuration, must be set to `"ldap"`.

## Health checks

The enterprise edition backend exposes health endpoints that can be called without credentials, for your load balancer or container orchestrator:

- `http://<baseURL>/actuator/health`: overall health of the server,
- `http://<baseURL>/actuator/health/liveness`: liveness probe,
- `http://<baseURL>/actuator/health/readiness`: readiness probe.
