# Deploying HQ

{% hint style="info" %}
Lenses HQ is prerequisite for installation of Lenses Agent
{% endhint %}

{% hint style="info" %}
Latest images:

Latest HQ image available [here](https://hub.docker.com/r/lensesio/lenses-hq) from Docker Hub.

Latest Lenses CLI as [tarball](https://archive.lenses.io/lenses/6.0/cli/) or as a [container](https://hub.docker.com/r/lensesio/lenses-cli).&#x20;
{% endhint %}

## Prerequisites

* Kubernetes 1.23+
* Helm 3.8.0+
* Available local Postgres database instance:
  * If you need to install Postgres on Kubernetes you can use one of the many publicly available Helm charts such as [Bitnami's](https://bitnami.com/stack/postgresql/helm).&#x20;
  * Or you can use one of the cloud provider's Postgres services such as one of these: [AWS](https://aws.amazon.com/rds/postgresql/), [Azure](https://azure.microsoft.com/en-us/products/postgresql), or [GCP](https://cloud.google.com/sql/postgresql).
  * See Lenses [docs here](https://docs.lenses.io/latest/getting-started/connecting-lenses-to-your-environment/overview#postgres) for information on configuring Postgres to work with HQ .&#x20;
  * username (and password) that has access to HQ database;
* [External Secrets Operator](https://external-secrets.io/latest/) is the only supported secrets operator.&#x20;

## Configure HQ

To configure Lenses HQ properly we have to understand the parameter groups that the Chart offers.

Under the **lensesHq** parameter there are some key parameter groups that are used to set up HQ:

1. [**storage**](#configure-storage-postgres)
   * definition of connection towards database (Postgres is **the only storage** option)&#x20;
2. [**auth**](#configure-auth-endpoint)
   * Password based authentication configuration
   * SAML / SSO configuration
   * definition of administrators or first users to access the HQ
3. [**http**](#configure-http-endpoint)
   * defines port under which HQ will be available for end users
   * defines values of special headers and cookies
   * types of connection such as TLS and non-TLS definitions
4. [**agents**](#configure-agents-connection-endpoint)
   * &#x20;defines connection between HQ and the Agent such as port where HQ will be listening for agent connections.
   * types of connection such as TLS and non-TLS definitions
5. **license**
6. [**monitoring**](#configure-metrics-endpoint)
   * controls the metrics settings where Prometheus alike metrics will be exposed
7. [**loggers**](#configure-logging)
   * definition of logging level for HQ

Moving forward, in the same order you can start configuring your Helm chart.

***

{% stepper %}
{% step %}

### Configure storage (Postgres)

{% hint style="info" %}
Postgres **is the only** available storage option.
{% endhint %}

Prerequisite:

* Running Postgres instance;
* Created database for HQ;
* Username (and password) which has access to the created database;

In order to successfully run HQ, ***storage*** within *values.yaml* has to be defined first.

Definition of *storage* object is as follows:

```yaml
lensesHq:
  storage:
    postgres:
      enabled: true
      host: ""
      port: 
      username: ""
      database: ""
      schema: ""
      tls: 
      params: {}
      passwordSecret:
        type: ""
```

Alongside Postgres password, which can be referenced / created through Helm chart, there are few more options which can help while setting up HQ.

#### Username reference types

There are two ways how username can be defined:

{% tabs %}
{% tab title="Plain Value" %}
The most straightforward way, if the username is not being changed, is by just defining it within the *username* parameter such as

{% code title="values.yaml" %}

```yaml
lensesHq:
  storage:
    postgres:
      enabled: true
      host: postgres-postgresql.postgres.svc.cluster.local
      port: 5432
      username: lenses
```

{% endcode %}
{% endtab %}

{% tab title="Reference from Secret" %}
In case Postgres *username* is being rotated or frequently changed it can be referenced from **pre-created** secret

{% code title="values.yaml" %}

```yaml
lensesHq:
  storage:
    postgres:
      enabled: true
      host: postgres-postgresql.postgres.svc.cluster.local
      port: 5432
      username: lenses
      useSecretForUsername:
        enabled: true
        existingSecret:
          name: my-secret
          key: username
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Password reference types

Postgres password can be handled in three ways using:

1. External Secret via [ExternalSecretOperator](https://external-secrets.io/latest/);
2. Pre-created secret;
3. Creating secret on the spot through **values.yaml;**

{% tabs %}
{% tab title="External Secrets" %}
{% hint style="warning" %}
To use this option, the External Secret Operator (ESO) has to be installed and available for usage in K8s cluster your are deploying HQ.
{% endhint %}

When specifying ***passwordSecret.type: "externalSecret",*** the chart will:

* create an ***ExternalSecret*** in the namespace where HQ is deployed;
* a secret is mounted for HQ to use.&#x20;

{% code title="values.yaml" %}

```yaml
lensesHq:
  storage:
    postgres:
      enabled: true
      host: postgres-postgresql.playground.svc.cluster.local
      port: 5432
      username: lenses
      database: lenseshq
      passwordSecret:
        type: "externalSecret"
        # Secret name where database password will be read from
        name: hq-password
        # Key name under secret where database password is stored
        key: password
        externalSecret:
          additionalSpecs: {}
          secretStoreRef:
            type: SecretStore # or ClusterSecretStore
            name: secretstore-secrets            
```

{% endcode %}
{% endtab %}

{% tab title="Pre-created secrets" %}
{% hint style="info" %}
Make sure that secret you are going to use is already created in namespace where HQ will be installed.
{% endhint %}

{% code title="values.yaml" %}

```yaml
 lensesHq:
   storage:  
     postgres:
       enabled: true
       host: postgres-postgresql.playground.svc.cluster.local
       port: 5432
       username: lenses
       database: lenseshq
       passwordSecret:
         type: "precreated"
         # Secret name where database password will be read from
         name: hq-password
         # Key from secret's data where database password is being stored
         key: postgres-password
```

{% endcode %}
{% endtab %}

{% tab title="Create New Secret" %}
{% hint style="danger" %}
This option is **NOT** for PRODUCTION usage but rather just for demo / testing.
{% endhint %}

The chart will create a secret with defined values below and the same secret will be read by HQ in order to connect to Postgres.&#x20;

{% code title="values.yaml" %}

```yaml
 lensesHq:
   storage:
     postgres:
       enabled: true
       host: [POSTGRES_HOSTNAME]
       port: 5432
       username: lenses
       database: lenseshq
       passwordSecret:
         type: "createNew"
         # name of a secret that will be created
         name: [K8s_SECRET_NAME]
         # Database password
         password: [DATABASE_USER_PASSWORD]
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Advanced Postgres settings

Sometimes to form the correct connection URI special parameters are needed. You can set the extra settings using *`params`**.***

**Example:**

{% code title="values.yaml" %}

```yaml
lensesHq:
  storage:
    postgres:
      enabled: true
      host: postgres-postgresql.postgres.svc.cluster.local
      port: 5432
      username: lenses
      params:
        sslmode: require
```

{% endcode %}
{% endstep %}

{% step %}

### Configure AUTH endpoint

{% hint style="info" %}
SAML / SSO is available only with Enterprise license.
{% endhint %}

The second pre-requirement to successfully run HQ is setting initial authentication.

You can choose between:

* password-based authentication, which requires users to provide a username and password;
* and SAML/SSO (Single Sign-On) authentication, which allows users to authenticate through an external identity provider for a seamless and secure login experience.

{% hint style="info" %}
Assertion Consumer Service endpoint is following

```
/api/v2/auth/saml/callback?client_name=SAML2Client
```

{% endhint %}

The definition of *auth* object is as follows:

{% code title="values.yaml" %}

```yaml
lensesHq:
  auth:
    users:
      - username: admin
        # bcrypt("correcthorsebatterystaple").
        password: $2a$10$F66cb6ZhnJjGCZuxlvKP1e84eytTpT1MDJcpBblHaZgsqp1/Aa0LG
    administrators:
      - admin
      - admin@example.com
      - admin2@example.com
    saml:
      enabled: true
      baseURL: ""
      entityID: ""
      # -- Example: <?xml version="1.0" ... (big blob of xml) </md:EntityDescriptor>
      metadata:
        referenceFromSecret: false
        secretName: ""
        secretKeyName: ""
        stringData: |
          <?xml version="1.0" encoding="UTF-8" standalone="no"?>
          </md:EntityDescriptor>
  
      userCreationMode: "sso"
      usersGroupMembershipManagementMode: "sso"
      uiRootURL: "/"
      groupAttributeKey: "groups"
      authnRequestSignature:
        enabled: false
```

{% endcode %}

**First** to cover is the users property. **Users Property:** The `users` property is defined as an array, where each entry includes a `username` and a `password`. Passwords **must be** hashed using bcrypt before being placed within the *password* property, for security purposes, ensuring that they are stored correctly and securely.

{% tabs %}
{% tab title="Inline Bcrypt hash" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  auth:
    users:
      - username: admin
        # bcrypt("correcthorsebatterystaple").
        password: $2a$10$F66cb6ZhnJjGCZuxlvKP1e84eytTpT1MDJcpBblHaZgsqp1/Aa0LG
```

{% endcode %}
{% endtab %}

{% tab title="Reading from secret" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  auth:
    users:
      - username: $(ADMIN_USER)
        password: $(ADMIN_USER_PWD)
  additionalEnv:
    - name: ADMIN_USER
      valueFrom:
        secretKeyRef:
          name: multi-credentials-secret
          key: user1-username
    - name: ADMIN_USER_PWD
      valueFrom:
        secretKeyRef:
          name: multi-credentials-secret
          key: user1-password
```

{% endcode %}

{% endtab %}
{% endtabs %}

**Second,** to cover will be *administrators.* It serves as the definition of user emails have the highest level of permissions upon authentication to HQ.&#x20;

**Third** attribute is *saml.metadata* field, needed for setting SAML / SSO authentication. In this step, you will need *metadata.xml* file which can be set in two ways:&#x20;

1. Referencing *metadata.xml* file through pre-created secret;
2. Placing *metadata.xml* contents inline as a string.

{% tabs %}
{% tab title="Reference from Secret" %}

<pre class="language-yaml"><code class="lang-yaml"><strong>lensesHq:
</strong>  auth:
    address: ":8080"
    accessControlAllowOrigin:
      - 
    administrators:
      - admin@example.com
      - admin2@example.com
    saml:
      baseURL: ""
      entityID: ""
      metadata:
        referenceFromSecret: true
        secretName: hq-tls-mock-saml-metadata
        secretKeyName: metadata.xml
      userCreationMode: "sso"
      usersGroupMembershipManagementMode: "manual"
</code></pre>

{% endtab %}

{% tab title="Inline String" %}

```yaml
lensesHq:
  auth:
    address: ":8080"
    accessControlAllowOrigin:
      - 
    administrators:
      - admin@example.com
      - admin2@example.com
    saml:
      baseURL: ""
      entityID: ""
      metadata:
        referenceFromSecret: false
        stringData: |
          <?xml version="1.0" encoding="UTF-8" standalone="no"?>
          ...
          ...
          </md:EntityDescriptor>
      userCreationMode: "sso"
      usersGroupMembershipManagementMode: "sso"
```

{% endtab %}
{% endtabs %}

In case SAML IdP requires certificate verification, same can be enabled and provided in the following way:

{% tabs %}
{% tab title="Reference certificate from Secret" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  auth:
    saml:
      authnRequestSignature:
        enabled: true
        authnRequestSigningCert:
          referenceFromSecret: true
          secretName: hq-agent-test-authority
          secretKeyName: hq-tls-test.crt.pem
        authnRequestSigningKey:
          secret:
            name: saml-test
            key: privatekey.key
```

{% endcode %}
{% endtab %}

{% tab title="Certificate through String" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  auth:
    saml:
      authnRequestSignature:
        enabled: true
        authnRequestSigningCert:
          stringData: |
            -----BEGIN CERTIFICATE-----
            ....
            -----END CERTIFICATE-----
        authnRequestSigningKey:
          secret:
            name: saml-test
            key: privatekey.key

```

{% endcode %}

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Configure HTTP endpoint

The third pre-requirement to successfully run HQ is the *`http`* definition. As previously mentioned, this parameter defines everything around the HTTP endpoint of the HQ itself and how users will interact with it.

Definition of HTTP object is as follows:

```yaml
lensesHq:
  http:
    address: ":8080"
    accessControlAllowOrigin:
      - 
    accessControlAllowCredentials: false
    secureSessionCookies: true
    tls:
      enabled: true
      cert:
      privateKey:
        secret:
          name: 
          key:
```

{% hint style="info" %}
Second part of HTTP definition would be enabling TLS and TLS definition itself. As previously defined for ***lensesHq.agents.tls*** same way of configuring TLS can be used for ***lensesHq*****.*****http*****.*****tls*** definition as well.
{% endhint %}
{% endstep %}

{% step %}

### Configure agent's connection endpoint

After correctly configuring the authentication strategy and connection endpoint, the agent handling is the last most important box to tick.&#x20;

The Agent's object is defined as follows:

```yaml
lensesHq:
  agents:
    # which port to listen on for agent requests
    address: ":10000" 
    tls:
      enabled: false
      verboseLogs: false
      cert:
      privateKey:
```

#### Enabling TLS

By default TLS for the communication between Agent and HQ is disabled. In case the requirement is to enable it, fthe ollowing has to be set:

* `lensesHq.agents.tls` *-* certificates to manage the connection between HQ and the Agents
* `lensesHq.http.tls`*-* certificates to manage connection with HQ's API

Unlike private keys which can be referenced and obtained only through a secret, Certificates can be referenced directly in **values.yaml** file as a string or as a secret.

{% tabs %}
{% tab title="Reference Certificate from Secret" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  agents:
    address: ":10000"
    tls:
      enabled: true
      cert:
        referenceFromSecret: true
        secretName: hq-agent-test-authority
        secretKeyName: hq-tls-test.crt.pem
      privateKey:
        secret:
          name: hq-agent-test-authority
          key: hq-tls-test.key.pem
```

{% endcode %}
{% endtab %}

{% tab title="Inline String" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  agents:
    address: ":10000"
    tls:
      enabled: true
      cert:
        stringData: |
          -----BEGIN CERTIFICATE-----
          ...
          ...
          -----END CERTIFICATE-----
      privateKey:
        secret:
          name: hq-agent-test-authority
          key: hq-tls-test.key.pem
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Configure license <a href="#configure-license" id="configure-license"></a>

{% hint style="success" %}
In demo purposes and testing the product you can use our community license

```yaml
license_key_2SFZ0BesCNu6NFv0-EOSIvY22ChSzNWXa5nSds2l4z3y7aBgRPKCVnaeMlS57hHNVboR2kKaQ8Mtv1LFt0MPBBACGhDT5If8PmTraUM5xXLz4MYv
```

{% endhint %}

License can be read in multiple ways:

* from a pre-created secret
* directly as a string defined in *values.yaml* file

{% tabs %}
{% tab title="Reference Certificate from Secret" %}
{% code title="values.yaml" %}

```yaml
lensesHq:
  license:
    referenceFromSecret: true
    secretName: hq-license
    secretKeyName: key
    acceptEULA: true
```

{% endcode %}
{% endtab %}

{% tab title="Inline string" %}

<pre class="language-yaml" data-title="values.yaml"><code class="lang-yaml">lensesHq:
  licence:
    referenceFromSecret: false
<strong>    stringData: "license_key_*"
</strong>    acceptEULA: true
</code></pre>

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Configure metrics endpoint

Metrics are optionally available in a Prometheus format and by default served on port **9090**.

The port can be changed in the following way:

{% code title="values.yaml" %}

```yaml
lensesHq:
  metrics:
    prometheusAddress: ":9090"
```

{% endcode %}
{% endstep %}
{% endstepper %}

***

### (Optional) Configure Ingress & Services

{% hint style="info" %}
Whilst the chart supports setting TLS on Lenses HQ itself we recommend placing it on the Ingress resource
{% endhint %}

Ingress and service resources are optionally supported.

{% hint style="warning" %}
The `http` ingress is intended only for HTTP/S traffic, while the `agents` ingress is designed specifically for TCP protocol. Ensure appropriate ingress configuration for your use case.
{% endhint %}

Enable an Ingress resource in the **values.yaml**:

{% code title="values.yaml" %}

```yaml
ingress:
  http:
    enabled: true
    annotations:
      traefik.ingress.kubernetes.io/router.entrypoints: websecure
    host: example.com
    ingressClassName: ""
    tls:
      enabled: false
      # The TLS secret must contain keys named tls.crt and tls.key that contain the certificate and private key to use for TLS.
      secretName: ""


  agent:
    enabled: true
    agentIngressConfig:
      apiVersion: traefik.containo.us/v1alpha1
      kind: IngressRouteTCP
      metadata:
        name: agents
      spec:
        entryPoints:
          - agents
        routes:
          - match: HostSNI(`example.com`)  # HostSNI to match TLS for TCP
            services:
              - name: lenses-hq            # Replace with your service name
                port: 10000                # Agent default TCP port  
        tls: {}

```

{% endcode %}

Enable a service resource in the **values.yaml**:

{% code title="values.yaml" %}

```yaml
# Lenses HQ service
service:
  enabled: true
  type: ClusterIP
  annotations: {}
  externalTrafficPolicy:
  loadBalancerIP: 130.211.x.x
  loadBalancerSourceRanges:
    - 0.0.0.0/0
```

{% endcode %}

***

### (Optional) Configure Service Accounts

Lenses HQ, by default, uses the **default** Kubernetes service account but you can choose to use a specific one.

If the user defines the following:

{% code title="values.yaml" %}

```yaml
# serviceAccount is the Service account to be used by Lenses to deploy apps
serviceAccount:
  create: true
  annotations: {}
  name: lenses-hq
```

{% endcode %}

The chart will create a new service account in the defined namespace for HQ to use.

***

### (Optional) Enable RBAC

There are two options you can choose between:

1. **rbacEnable: true** - will enable the creation of **ClusterRole** and **ClusterRoleBinding** for the service account mentioned in the snippet above<br>
2. **rbacEnable: true** and **namespaceScope: true** - will enable the creation of Role and RoleBinding which is more restrictive.

***

### Configure logging

There are different logging modes and levels that can be adjusted.

{% code title="values.yaml" %}

```yaml
lensesHq:
  logger:
    # Allowed values are: text | json
    mode: "text"

    # Allowed values are: info | debug
    level: "info"
```

{% endcode %}

***

## Add chart repository

First, add the Helm Chart repository using the Helm command line:

```bash
helm repo add lensesio https://helm.repo.lenses.io/
helm repo update
```

{% embed url="<https://github.com/lensesio/lenses-helm-charts/tree/release/6.0>" %}

## Installing HQ

{% hint style="info" %}
Be aware that for the time being and for alpha purposes usage of `--version`is mandatory when deploying Helm chart through Helm repository.
{% endhint %}

{% code title="terminal" %}

```bash
helm install lenses-hq lensesio/lenses-hq \
   --values values.yaml \
   --create-namespace --namespace lenses-hq \
   --version 6.0.8
```

{% endcode %}

## Example Values files

{% hint style="info" %}
Be aware that example of *values.yaml* shows only how all parameters should look at the end. Please fill them with correct values otherwise Helm installation might not be successful.&#x20;
{% endhint %}

<details>

<summary>Example of <em>values.yaml</em></summary>

More about default values for Lenses HQ Helm Chart can be found in [*values.yaml*](https://github.com/lensesio/lenses-helm-charts/blob/release/6.0/charts/lenses-hq/values.yaml)*.* An example is below:

{% code title="values.yaml" %}

```yaml
resources:
  requests:
  #   cpu: 1
    memory: 4Gi
  limits:
  #   cpu: 2
    memory: 8Gi

image:
  repository: lensesio/lenses:latest

rbacEnable: true
namespaceScope: true

ingress:
  http:
    enabled: true
    annotations:
      traefik.ingress.kubernetes.io/router.entrypoints: websecure
    host: example.com

lensesHq:
  agents:
    address: ":10000"
    tls:
      enabled: true. # optional
      cert:
        referenceFromSecret: true
        secretName: hq-agent-test-authority
        secretKeyName: hq-tls-test.crt.pem
      privateKey:
        secret:
          name: hq-agent-test-authority
          key: hq-tls-test.key.pem
  auth:
    users:
      - username: admin
        # bcrypt("correcthorsebatterystaple").
        password: $2a$10$F66cb6ZhnJjGCZuxlvKP1e84eytTpT1MDJcpBblHaZgsqp1/Aa0LG
    administrators:
      - admin
      - admin@example.com
      - admin2@example.com
    saml:
      enabled: true    # optional
      baseURL: ""
      entityID: ""
      # -- Example: <?xml version="1.0" ... (big blob of xml) </md:EntityDescriptor>
      metadata:
        referenceFromSecret: false
        secretName: ""
        secretKeyName: ""
        stringData: |
          <?xml version="1.0" encoding="UTF-8" standalone="no"?>
          </md:EntityDescriptor>
  
      userCreationMode: "sso"
      usersGroupMembershipManagementMode: "sso"
      uiRootURL: "/"
      groupAttributeKey: "groups"
  http:
    address: ":8080"
    accessControlAllowOrigin:
      - 
    accessControlAllowCredentials: false
    secureSessionCookies: true
    tls:
      enabled: true    # optional
      cert:
      privateKey:
        secret:
          name: 
          key:
  # Find more details in https://docs.lenses.io/current/installation/kubernetes/helm/#helm-storage
  ## Postgres template example: "postgres://[username]:[pwd]@[host]:[port]/[database]?sslmode=require"
  storage:
    postgres:
      enabled: true
      host: [POSTGRES_HOST_URL]
      port: 5432
      username: [POSTGRES_USERNAME]
      database: [POSTGRES_USER_PWD]
      passwordSecret:
        type: "precreated"
        name: initContainer-2-db-secret
        key: password
```

{% endcode %}

</details>

## What's next?

After the successful configuration and installation of HQ, the next steps would be:

1. [**Deploying and Agent**](https://docs.lenses.io/latest/devx/6.0/deployment/installation/helm/agent)
2. [**Configuring IAM roles / groups / policies**](https://docs.lenses.io/latest/devx/6.0/user-guide/iam/example-policies)
