# Secure the KubeMQ Connection with TLS and Auth (/integrations/keda/how-to/tls-and-auth)



When your KubeMQ broker requires TLS and an authentication token, the scaler must present matching credentials on the connection it opens to the broker. This guide configures the **scaler-to-KubeMQ hop** — the gRPC call the scaler makes to read the `Waiting` count via `ListQueuesChannels`. It does **not** change how the scaler's own gRPC server (the one KEDA talks to) listens; that server is plaintext by design in v1 (see the warning at the end).

The pieces involved are:

* A **CA certificate** mounted into the scaler pod so it can verify the broker's TLS certificate.
* A **TriggerAuthentication** that feeds the auth token into the scaler from a Kubernetes `Secret`, so the token never appears in the `ScaledObject` manifest.
* A &#x2A;*`ScaledObject`** that turns on TLS, points at the mounted CA file, and references the `TriggerAuthentication`.

## Prerequisites [#prerequisites]

* The scaler deployed via Helm (see [Getting Started](/integrations/keda/tutorials/getting-started)).

* A KubeMQ broker that is configured for TLS and requires an auth token. You can run one locally for testing:

  <RunKubeMQ ports="[50000, 9090]" />

  Port `50000` is the gRPC port the scaler connects to via `kubemqAddress`; port `9090` is the shared HTTP port used by connector endpoints.

* The broker's CA certificate (`ca.pem`) and the auth token, available to load into Kubernetes.

## Step 1 — Mount the CA Certificate into the Scaler Pod [#step-1--mount-the-ca-certificate-into-the-scaler-pod]

The scaler reads the CA file from a path inside its own container, so the certificate has to be mounted there first. Store it in a Kubernetes `Secret` and mount that `Secret` at `/certs` using the chart's `extraVolumes` / `extraVolumeMounts` values.

<Steps>
  <Step>
    Create the `Secret` holding the CA certificate:

    ```bash title="Create the CA Secret"
    kubectl create secret generic kubemq-ca-cert \
      --from-file=ca.pem=./ca.pem
    ```
  </Step>

  <Step>
    Mount it into the scaler pod at `/certs`. The `extraVolumes` and `extraVolumeMounts` values default to empty arrays in the chart, so you supply both:

    ```bash title="Mount the CA at /certs via Helm"
    helm install kubemq-keda-scaler deploy/helm/kubemq-keda-scaler/ \
      --set extraVolumes[0].name=certs \
      --set extraVolumes[0].secret.secretName=kubemq-ca-cert \
      --set extraVolumeMounts[0].name=certs \
      --set extraVolumeMounts[0].mountPath=/certs \
      --set extraVolumeMounts[0].readOnly=true
    ```

    This makes the certificate available inside the pod as `/certs/ca.pem`, which is the path you will reference from the `ScaledObject`.
  </Step>
</Steps>

<Callout type="info">
  `/certs` is one of the directories the scaler accepts for `certFile`. See [the `certFile` path restriction](#the-certfile-path-restriction) below before choosing a different mount point.
</Callout>

## Step 2 — Create the TriggerAuthentication and Secret [#step-2--create-the-triggerauthentication-and-secret]

Keep the auth token out of the `ScaledObject` by storing it in an Opaque `Secret` and exposing it through a KEDA `TriggerAuthentication`. The `secretTargetRef` maps the scaler's `authToken` parameter to a key inside the `Secret`.

```yaml title="trigger-auth.yaml"
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: kubemq-trigger-auth
spec:
  secretTargetRef:
    - parameter: authToken
      name: kubemq-auth-secret
      key: token
---
apiVersion: v1
kind: Secret
metadata:
  name: kubemq-auth-secret
type: Opaque
stringData:
  token: "your-kubemq-auth-token"
```

Apply it before the `ScaledObject`:

```bash title="Apply the TriggerAuthentication and Secret"
kubectl apply -f trigger-auth.yaml
```

The `parameter: authToken` value is what wires the secret into the scaler — KEDA injects the `Secret`'s `token` value as the `authToken` metadata field when it calls the scaler.

## Step 3 — Create the TLS + Auth ScaledObject [#step-3--create-the-tls--auth-scaledobject]

The `ScaledObject` turns on TLS, points `certFile` at the mounted CA, optionally sets a TLS server-name override, and references the `TriggerAuthentication` so the token is supplied from the `Secret` rather than inline.

```yaml title="scaled-object-tls-auth.yaml"
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: secure-queue-scaler
spec:
  scaleTargetRef:
    name: secure-consumer
  pollingInterval: 15
  cooldownPeriod: 60
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
    - type: external
      metadata:
        scalerAddress: <SCALER_SERVICE>.<NAMESPACE>.svc.cluster.local:9090
        kubemqAddress: kubemq.default.svc.cluster.local:50000
        queueName: secure-queue
        targetWaiting: "10"
        tls: "true"
        certFile: /certs/ca.pem
        serverOverrideDomain: kubemq.local
      authenticationRef:
        name: kubemq-trigger-auth
```

Replace `<SCALER_SERVICE>` and `<NAMESPACE>` with your scaler Service name and namespace. `serverOverrideDomain` is optional — include it only when the broker's certificate Common Name / SAN differs from the address you connect to (for example, connecting via a Service DNS name while the certificate is issued for `kubemq.local`).

Apply it:

```bash title="Apply the ScaledObject"
kubectl apply -f scaled-object-tls-auth.yaml
```

## Metadata Fields [#metadata-fields]

These are the metadata fields involved in securing the broker connection. All four are optional; supply only what your broker requires.

<TypeTable
  type="{
  authToken: {
    description: &#x22;KubeMQ authentication token. Supplied through a TriggerAuthentication rather than inline. Maximum 4096 characters.&#x22;,
    type: &#x22;string&#x22;,
  },
  tls: {
    description: &#x22;Enable a TLS connection to the broker. Accepts true, 1, or yes (case-insensitive); anything else is treated as false.&#x22;,
    type: &#x22;string&#x22;,
    default: &#x22;false&#x22;,
  },
  certFile: {
    description: &#x22;Path to the CA certificate file inside the scaler pod. Must resolve under /certs/, /etc/ssl/, or /etc/pki/.&#x22;,
    type: &#x22;string&#x22;,
  },
  serverOverrideDomain: {
    description: &#x22;TLS server name override, used when the broker certificate name differs from the connection address.&#x22;,
    type: &#x22;string&#x22;,
  },
}"
/>

## The certFile Path Restriction [#the-certfile-path-restriction]

The scaler validates `certFile` before it ever opens a connection. The cleaned path must begin with one of three allowed prefixes and must not contain `..`:

```go title="scaler/config.go"
var allowedCertPrefixes = []string{"/certs/", "/etc/ssl/", "/etc/pki/"}

func validateCertPath(path string) error {
	cleaned := filepath.Clean(path)
	if strings.Contains(cleaned, "..") {
		return status.Error(codes.InvalidArgument, "certFile must not contain path traversal")
	}
	for _, prefix := range allowedCertPrefixes {
		if strings.HasPrefix(cleaned, prefix) {
			return nil
		}
	}
	return status.Errorf(codes.InvalidArgument, "certFile must be under one of: %v", allowedCertPrefixes)
}
```

If the path falls outside those directories or contains path traversal (`..`), the scaler rejects the metadata with gRPC `InvalidArgument` and never connects. The path is also capped at 512 characters. Mounting the CA at `/certs` (Step 1) keeps you inside the allowed set with no extra configuration.

## How the Scaler Applies These Options [#how-the-scaler-applies-these-options]

When the scaler builds its KubeMQ client, it translates the metadata into `kubemq-go` client options. The auth token and TLS settings are only added when present, and a `Ping` immediately after connect makes bad credentials fail fast instead of surfacing later during a poll:

```go title="scaler/kubemq_client.go"
opts := []kubemq.Option{
	kubemq.WithAddress(meta.KubeMQHost, meta.KubeMQPort),
	kubemq.WithClientId("kubemq-keda-scaler"),
	kubemq.WithCheckConnection(true),
}

if meta.AuthToken != "" {
	opts = append(opts, kubemq.WithAuthToken(meta.AuthToken))
}

if meta.TLS {
	opts = append(opts, kubemq.WithTLS(meta.CertFile))
	if meta.ServerOverrideDomain != "" {
		opts = append(opts, kubemq.WithServerNameOverride(meta.ServerOverrideDomain))
	}
}

client, err := kubemq.NewClient(ctx, opts...)
if err != nil {
	return nil, err
}

if _, err := client.Ping(ctx); err != nil {
	_ = client.Close()
	return nil, fmt.Errorf("kubemq: ping failed after connect: %w", err)
}
```

* `WithAuthToken` attaches the token from your `Secret`.
* `WithTLS(certFile)` enables TLS using the mounted CA certificate.
* `WithServerNameOverride` applies `serverOverrideDomain` only when you set it.
* The `Ping` after connect fails fast on bad credentials or an unreachable broker.

## Apply Order [#apply-order]

The `TriggerAuthentication` (and its backing `Secret`) must exist before the `ScaledObject` references it, otherwise KEDA cannot resolve `authenticationRef`. The example files in `deploy/examples/` are meant to be applied in this order:

| File                          | Description                                     | Prerequisites                     |
| ----------------------------- | ----------------------------------------------- | --------------------------------- |
| `trigger-auth.yaml`           | `TriggerAuthentication` with `Secret` reference | Create the `Secret` first         |
| `scaled-object-tls-auth.yaml` | TLS + auth token `ScaledObject`                 | `trigger-auth.yaml` applied first |

In short: create the CA `Secret`, apply `trigger-auth.yaml`, then apply `scaled-object-tls-auth.yaml`.

<Callout type="warn">
  TLS here secures only the **scaler-to-broker** hop. The scaler's own gRPC server — the one the KEDA operator calls — runs **plaintext by design in v1** and is meant to be a `ClusterIP` Service reachable only inside the cluster. Protect that port with a `NetworkPolicy` restricting ingress to the KEDA operator namespace.
</Callout>

## Error Behavior [#error-behavior]

If the broker rejects the scaler's credentials, the scaler maps the KubeMQ error to a gRPC status code so KEDA can apply its `fallback` strategy:

* An **authentication failure** maps to gRPC `Unauthenticated`.
* A **permission denial** maps to gRPC `PermissionDenied`.

Both are returned as errors (never a fake `Waiting=0`), so KEDA falls back to your configured `fallback.replicas` instead of scaling the deployment to zero on a transient credential problem.

## Related [#related]

* [ScaledObject metadata](/integrations/keda/reference/scaled-object-metadata) for the complete metadata table, including the `tls`, `certFile`, and `authToken` fields.
* [Error codes](/integrations/keda/reference/error-codes) for the full KubeMQ-error-to-gRPC-status mapping.
* [Getting Started](/integrations/keda/tutorials/getting-started) to install the scaler and create your first `ScaledObject`.
