Secrets Management on Kubernetes with ENV Provider

We used the Sensu Go K8s Quickstart Template to spin up a 3 Node Sensu Go Cluster hosted on Kubernetes - GitHub - sensu/sensu-k8s-quick-start - as we have a lot of Checks that require Username and Password we need to get Secrets Management up and running.

We followed the Guide to get certificates for mTLS installed and Agents are communicating, but we’re not really getting Secrets Management Up and Running.

Creating a /etc/default/sensu-backend (k8s pods are using Alpine Linux 3.8.5) file does not seem to work, we also tried to directly create Environment Variables on Backend Pods but still no success. Only thing that seems to work is, if we do both but is this really required ? btw. Hashicorp Vault is not an option for us … anyone else running Sensu Go on k8s and having experience with Secrets Management ?

1 Like

Hi there, @seizste :wave: great question!

The /etc/default/sensu-backend approach relies on process management (e.g. systemd or sysvinit) which would not normally be present in a containerized environment, so I wouldn’t recommend that approach.

The Sensu Go env Secrets Provider should work directly with Kubernetes own built-in secrets management. In practice, Kubernetes secrets are discrete K8s resources that make secrets available to reference from various pod controllers (e.g. StatefulSets); in other words, you have to create a K8s Secret, and then also reference it to actually use it somewhere.

Here’s a few example K8s secrets:

---
apiVersion: v1
kind: Secret
metadata:
  name: influxdb
type: Opaque
stringData:
  addr: http://influxdb-0.influxdb.sensu-system.svc.cluster.local:8086
  db: sensu 
  user: admin 
  password: password

---
apiVersion: v1
kind: Secret
metadata:
  name: servicenow
type: Opaque
stringData:
  host: xxxxxxxx.service-now.com/
  username: admin 
  password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  cmdb_ci_table: cmdb_ci 
  event_table: event 
  incident_table: incident  

And here’s an example excerpt from a StatefulSet resource, which fetches the value of a K8s Secret and exposes it as an environment variable:

containers:
- name: sensu-backend
  image: sensu/sensu:5.20.1
  command: ...
  env:
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: INFLUXDB_ADDR
    valueFrom:
      secretKeyRef:
        name: influxdb
        key: addr
  - name: INFLUXDB_DB
    valueFrom:
      secretKeyRef:
        name: influxdb
        key: db
  - name: INFLUXDB_USER
    valueFrom:
      secretKeyRef:
        name: influxdb
        key: user
  - name: INFLUXDB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: influxdb
        key: password

In this example, I’m fetching the values of my influxdb secret and mapping them as environment variables (INFLUXDB_ADDR, INFLUXDB_DB, INFLUXDB_USER, and INFLUXDB_PASSWORD) which will be accessible from the sensu-backend container in this StatefulSet pod.

I hope this helps!

1 Like

Hmm maybe for completeness we need a simple example for k8s sidecar using a k8s secret in the k8s quick start example.

One more thing… you should be able to troubleshoot/verify that these environment variables are being correctly exposed by using kubectl exec.

https://kubernetes.io/docs/tasks/debug-application-cluster/get-shell-running-container/

Here’s a quick example/demo:

2 Likes

Hello @calebhailey,

thanks for the detailed help. I finally got it working using the following steps / configs in k8s and sensu.

k8s-secret

apiVersion: v1
data:
  es-pwd-logging-and-metric: P@ssw0rd!
kind: Secret
metadata:
  name: sensu-env-secrets
  namespace: sensu-system
type: Opaque

k8s-deployment
...
env:
  - name: SENSU_ES_PWD_LOGGING_AND_METRIC
    valueFrom:
      secretKeyRef:
        name: sensu-env-secrets
        key: es-pwd-logging-and-metric
...

sensu-secret

type: Secret
api_version: secrets/v1
metadata:
  name: es_pwd_logging_and_metric
  namespace: default
spec:
  id: SENSU_ES_PWD_LOGGING_AND_METRIC
  provider: env

sensu-check

type: CheckConfig
api_version: core/v2
metadata:
  name: es-mgmt-and-monitoring-health
  namespace: default
spec:
  command: check-es-cluster-health.rb -h server.test.cloud -p 9243 -l cluster 
    -u elastic -P $ES_PWD
interval: 300
publish: true
runtime_assets:
  - sensu-plugins-elasticsearch
  - sensu-ruby-runtime
  secrets:
  - name: ES_PWD
    secret: es_pwd_logging_and_metric
subscriptions:
  - linux
2 Likes

Just for my understanding Do these env variables need to be available on the backend or the agents?
I currently do have a check where I have exposed secrets using env variables on the agent.
If backend only is fine I will move them there.

1 Like

@raulgs great question! The real value-add of Sensu’s built-in Secrets Management capabilities is that you only need to define the secrets on the backends. However, in order for Sensu to transmit those secrets to agents for use in checks, you must have secured agent-to-sever communication (TLS encrypted transport) and be using mTLS agent authentication. This is explained in the Secrets reference documentation, here:

Secrets are only transmitted over a transport layer security (TLS) websocket connection. Unencrypted connections must not transmit privileged information. For checks, hooks, and assets, you must enable mutual TLS (mTLS). Sensu will not transmit secrets to agents that do not use mTLS.

Sensu only exposes secrets to Sensu services like environment variables and automatically redacts secrets from all logs, the API, and the dashboard.

This guide covers both topics:

I hope this helps!

1 Like

@calebhailey … a follow up question, where can i use secrets to replace something in the config of a check or handler … or asked differently, is the following code valid ?

type: Handler
api_version: core/v2
metadata:
  name: dnscheck-1486439-prod
spec:
  command: sensu-go-elasticsearch --index metrics-sensu-dnscheck-1486439-prod --full_event_logging
  env_vars:
    - $ES_SENSU_CLOUD_ID
  runtime_assets:
    - sensu-go-elasticsearch
  secrets:
    - name: ES_SENSU_CLOUD_ID
      secret: es_pwd_1486439_prod
  timeout: 0
  type: pipe

Hey @seizste :wave:

You’re on the right track! A few observations/comments:

  • Sensu Secrets are directly exposed as environment variables, so you do not need to set an environment variable in addition to the secret in the handler definition (in your example handler definition you have an env_var and a secret called ES_SENSU_CLOUD_ID, which is not necessarily an invalid configuration, but it’s most likely going to cause some conflict).

  • Because Sensu Secrets read secret values and expose them to the check or handler as environment variables, the plugin/command being executed by Sensu either needs to implicitly read those environment variables, or they must be passed into the command via a command line argument; for example:

    myplugin.rb --url=${MY_SECRET}
    
  • It looks like the Elasticsearch plugin you’re using supports a single environment variable (ELASTICSEARCH_URL ) with a value like https://user:pass@hostname:port. It appears that your Kubernetes secret only contains the Elasticsearch “password”, so you’ll either need to construct the ELASTICSEARCH_URL in the handler command, or modify the contents of your secret and expose the secret as ELASTICSEARCH_URL instead of ES_SENSU_CLOUD_ID

Here’s a few example Sensu secret + handler definitions to help explain what I mean by this last comment:

Pass secret an explicit environment variable in the command

---
type: Secret
api_version: secrets/v1
metadata:
  name: es_pwd_1486439_prod
spec:
  provider: env
  id: SENSU_ES_PWD_LOGGING_AND_METRIC
---
type: Handler
api_version: core/v2
metadata:
  name: dnscheck-1486439-prod
spec:
  command: >-
    ELASTICSEARCH_URL="https://admin:${ES_SENSU_CLOUD_ID}@elasticsearch.yourdomain.com:9243" &&
    sensu-go-elasticsearch --index metrics-sensu-dnscheck-1486439-prod --full_event_logging
  runtime_assets:
    - sensu-go-elasticsearch
  secrets:
    - name: ES_SENSU_CLOUD_ID
      secret: es_pwd_1486439_prod
  timeout: 0
  type: pipe

Pass secret as implicit environment variable

---
type: Secret
api_version: secrets/v1
metadata:
  name: sensu_es_url
spec:
  provider: env
  id: SENSU_ES_URL
---
type: Handler
api_version: core/v2
metadata:
  name: dnscheck-1486439-prod
spec:
  command: sensu-go-elasticsearch --index metrics-sensu-dnscheck-1486439-prod --full_event_logging
  runtime_assets:
    - sensu-go-elasticsearch
  secrets:
    - name: ELASTICSEARCH_URL
      secret: sensu_es_url
  timeout: 0
  type: pipe

NOTE: In this latter example, you’d need to modify your Kubernetes secret so that the value of the secret is in the format of: https://user:pass@hostname:port.

I hope this helps!

1 Like

Thanks a lot @calebhailey … i went for the second option and it works like a charm !

2 Likes

Awesome! Glad to hear it!