Securing your Kubernetes deployment

Prerequisites

Install required security tools:

  • NSC
  • mir tools install

Have a mir deployment ready to be used:

Setup

Mir Security CLI wraps NSC commands with a set of preset to make securing Mir ecoystem easier. It offers a set of basic commands to manipulate credentials. Moreover, it offers premade scope for the three types of users in Mir:

  • Modules (server components)
  • Clients (access CLI and other frontend)
  • Devices (connect devices)

The CLI uses the current context to help manage which server to target. Use mir config editto add a new context with server name and url:

# If using local k3d
- name: k3d
  target: nats://localhost:31422
  grafana: grafana-local:8081

You can overwrite the Operator, Account and URL arguments using flags. This requires more familiarity with the NSC tool. Moreover, you can use flag --no-exec on each command to see NSC commands.

Step 1: Initialize Mir Operator

Create a new NATS operator for your deployment:

mir tools security init

This creates:

  • Operator signing keys
  • Default account named Mir
  • System account for internal operations

Step 2: Configure NATS Server

Generate Resolver Configuration used to launch Nats Server:

# Generate NATS resolver configuration
mir tools security generate-resolver -p ./resolver.conf

Using Values file

Replace the OPERATOR_JWT, SYS_ACCOUNT_ID, and SYS_ACCOUNT_JWT with your values. Make sure that you do not include the trailing , in the SYS_ACCOUNT_JWT.

# values-auth.yaml
## Nats Config
nats:
  config:
    resolver:
      enabled: true
      merge:
        type: full
        interval: 2m
        timeout: 1.9s
    merge:
      operator: OPERATOR_JWT
      system_account: SYS_ACCOUNT_ID
      resolver_preload:
        SYS_ACCOUNT_ID: SYS_ACCOUNT_JWT

Using Secret

To use a secret instead, we need to transform the resolver.conf as a Kubernetes secret:

# Directly to the cluster
kubectl create secret generic mir-resolver-secret --from-file=resolver.conf
# As file
kubectl create secret generic mir-resolver-secret --from-file=resolver.conf --dry-run=client -o yaml > mir-resolver.secret.yaml

Alternatively, you can use the --kubernetes flag to combine credetials and secret creation:

mir tools security generate-resolver -p ./mir-resolver.secret.yaml --kubernetes

We create a new volume to mount the file and then we refer the secret. Update <MIR_RESOLVER_SECRET> with your secret name.

nats:
  config:
    resolver:
      enabled: true
      merge:
        type: full
        interval: 2m
        timeout: 1.9s
    merge:
      $include: ../nats-auth/resolver.conf
  container:
    patch:
    - op: add
      path: /volumeMounts/-
      value:
        name: nats-auth-include
        mountPath: /etc/nats-auth/
  podTemplate:
    patch:
    - op: add
      path: /spec/volumes/-
      value:
        name: nats-auth-include
        secret:
          secretName: <MIR_RESOLVER_SECRET>

The server will now be running with authorization.

Step 3: Create Module Credentials

Let's get the Mir server up & running by generating credentials taylored for it.

# Create new user of type Module
mir tools security add module mir_srv
# Sync user with server
mir tools security push
# Create credentials file for it
mir tools security generate-creds mir_srv -p ./mir_srv.creds

Now time to create the Kubernetes Secret and Configure the Mir Server with it.

# Directly to the cluster
kubectl create secret generic mir-auth-secret --from-file=mir.creds=mir_srv.creds
# As file
kubectl create secret generic mir-auth-secret --from-file=mir.creds=mir_srv.creds --dry-run=client -o yaml > mir-auth.secret.yaml
# Note: the key `mir.creds` is required

Alternatively, you can use the --kubernetes flag to combine credetials and secret creation:

mir tools security generate-creds mir_srv -p ./mir-auth.secret.yaml --kubernetes

Finally, let's refer the secret in the values-auth.yaml and restart the server:

# values-auth.yaml
## Nats Config
nats:
  config:
    resolver:
      enabled: true
      merge:
        type: full
        interval: 2m
        timeout: 1.9s
  ...

## Mir Server
authSecretRef: "mir-auth-secret"

Start server using helm [install|upgrade] <name> <path> -f values-auth.yaml.

You should see a successfull connection withour any errors from the Mir Server pod.

To validate authorization is probably setup, run mir device ls and you should see nats: Authorization Violation.

Step 4: Create Client Credentials

Let's create a Client credentials to have full access to the system.

# Create new user of type Client
# use -h to see options
mir tools security add client ops --swarm
# Sync user with server
mir tools security push
# Generate credentials
mir tools security generate-creds ops -p ./ops.creds

Edit CLI configuration file to add the credentials mir tools config edit:

- name: local
  target: nats://localhost:4222
  grafana: localhost:3000
  credentials: <path>/ops.creds

If you run mir dev ls, you should now see the list of devices.

Step 5: Create Device Credentials

The last type of users is of Device type. Refer to Integrating Mir to create a device.

# Create new user of type Device
# add --wildcard to have the same credentials for all devices.
# Else it is bound to this device id.
mir tools security add device dev1
# Sync user with server
mir tools security push
# Generate credentials
mir tools security generate-creds dev1 -p ./dev1.creds

There are a few options to load the credentials file with the DeviceSDK.

# Using Builder with fix path
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    WithCredentials("/<path>/dev1.creds").
    WithDeviceId("dev1").
    Build()
# Using Builder with default lookup
#   ./device.creds
#   ~/.config/mir/device.creds
#   /etc/mir/device.creds
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    DefaultUserCredentialsFile().
    Build()

It is also possible to load the credentials from the config file:

# Using Builder with config file
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    DefaultConfigFile().
    Build()
mir:
  credentials: "<path>/dev1.creds"
  device:
    id: "dev1"

Run the device and no auth errors should be displayed. Now run mir dev ls and you should see:

➜ mir dev ls
NAMESPACE/NAME                                DEVICE_ID        STATUS     LAST_HEARTHBEAT      LAST_SCHEMA_FETCH    LABELS
default/dev1                                  dev1             online     2025-09-18 16:16:27  2025-09-18 16:15:18

Other commands

# View current operator configuration
mir tools security env

# Sync with remote credential store
mir tools security push
mir tools security pull

# List users
mir tools security list [operators|accounts|users]

Summary

Mir's integration with NATS security provides:

  • Strong authentication using JWT and nkeys
  • Flexible authorization with subject-based permissions
  • Simple management through the Mir CLI
  • Production-ready security model for IoT deployments

For additional security features, see: