Skip to content

Commit

Permalink
Document TLS client configuration (#1031)
Browse files Browse the repository at this point in the history
* doc: Update ECS interop TLS configuration

* doc: Document JS interop TLS configuration

* doc: Document AWS key vault configuration with Private CA support
  • Loading branch information
johanstokking committed Jan 24, 2023
1 parent 5dba9d4 commit a425880
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 116 deletions.
123 changes: 22 additions & 101 deletions doc/content/getting-started/aws/ecs/interop/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,119 +4,40 @@ description: "LoRaWAN Backend Interfaces"
weight: 8
---

{{% tts %}} exposes the Join Server API defined by LoRaWAN Backend Interfaces 1.0 and 1.1. This is used for interoperability between LoRaWAN networks.
{{% tts %}} exposes a subset of the Join Server API defined by LoRaWAN Backend Interfaces 1.0 and 1.1. This is used for interoperability between LoRaWAN networks.

<!--more-->

The functionality can be exposed either by the Join Server or by the Identity Server, but not both at once. Join Server is capable of handling the join flow, that is `HomeNSReq`, `JoinReq` and `AppSKeyReq` requests. Identity Server can only answer to `HomeNSReq`. Typically, you should enable interoperability if you want to use device activations through Packet Broker. You can use Join Server interoperability if you want to expose the entire join flow to other LoRaWAN network servers.
The functionality is exposed by the Identity Server. Identity Server answers to `HomeNSReq` to inform networks about the home network of an end device when they try to join a LoRaWAN network. Packet Broker is the typical consumer of this: enable interoperability if you want to use device activations through Packet Broker.

# TLS

Normally, AWS Load Balancer terminates TLS, and then forwards unencrypted packets to {{% tts %}} via AWS internal network. This assures that all clients connecting to {{% tts %}} are certain of its authenticity.
You can choose between AWS Load Balancer or a reverse proxy to terminate TLS in your deployment.

This doesn't work the other way around though, as AWS Load Balancer doesn't verify client certificates. It is up to you where you choose to terminate TLS:
The benefit of AWS Load Balancer is that it can use ACM managed certificates that are automatically renewed by AWS. However, AWS Load Balancer does not support TLS client authentication. If you need TLS client authentication for LoRaWAN Backend Interfaces interoperability, let the proxy terminate TLS.

## TLS termination by the AWS Load Balancer
## TLS termination by AWS Load Balancer

When parameters `InteropEnabled`, `InteropEnabledIS` and `InteropEnabledJS` in templates `3-2-load-balancer`, `5-3a-ecs-is-service` and `5-4-ecs-services` are set to `server-only-authentication`, TLS is terminated by the AWS Load Balancer, just like any other connection. This is only suitable for use cases where you don't need to verify client authenticity.
This requires the following settings:

## TLS termination by {{% tts %}}
Template | Parameter | Value
--- | --- | ---
`3-2-load-balancer` | `SupportProxyTLS` | `false`
`3-2-load-balancer` | `InteropEnabled` | `true`
`5-3a-ecs-is-service` | `InteropEnabledIS` | `true`
`5-4-ecs-services` | `SupportProxyTLS` | `false`

When parameters `InteropEnabled`, `InteropEnabledIS` and `InteropEnabledJS` in templates `3-2-load-balancer`, `5-3a-ecs-is-service` and `5-4-ecs-services` are set to `mutual-authentication`, TLS-encrypted connections are forwarded to {{% tts %}}, which terminates the encryption. This allows the stack to verify client certificates stored in the S3 bucket.
## TLS termination by the proxy

# Join Server interoperability
Template | Parameter | Value
--- | --- | ---
`3-2-load-balancer` | `SupportProxyTLS` | `true`
`3-2-load-balancer` | `InteropEnabled` | `true`
`5-3a-ecs-is-service` | `InteropEnabledIS` | `true`
`5-4-ecs-services` | `SupportProxyTLS` | `true`

## Deployment
In this scenario, Certbot requests Let's Encrypt certificates.

In this setup, you are assumed to use two accounts: one account for {{% tts %}}, and another one only for the Crypto Server.
# Packet Broker settings

### Step 1: deploy {{% tts %}}

Deploy {{% tts %}} normally. For the `PeerRequesterAccountId` parameter in `1-1-vpc`, use the ID of the account where you want to deploy Crypto Server. Do not enable interop and assume that {{% tts %}} doesn't connect to the Crypto Server.

### Step 2: add certificates

For the server certificate:
- if you use `server-only-authentication`, AWS Load Balancer will use existing certificate
- if you use `mutual-authentication`, then use the certbot task to request a certificate. It will be put into AWS Secrets Manager, and then fetched by {{% tts %}}

If you're using `mutual-authentication`, you need to populate client certificates stored in the interop config bucket, created in template `2-4b-routing-s3`. In this bucket you need to place a file `config.yml`, which contains a map `<net-id>: <ca.pem>`. Example configuration:

```yaml
000000: ca-000000.pem
000009: ca-000009.pem
```
### Step 3: deploy the Crypto Server
Deploy the template `200-1-crypto`. This template is designed to be deployed in a separate account, but same region. No other templates are needed for Crypto Server, this is a self-contained deployment.

- the tuple `${Cluster}.${Environment}.${NetworkName}` should NOT be identical. We suggest using the same `NetworkName` and `Environment`, but different `Cluster`
- the `CidrBlock` parameter should NOT be identical
- the `ClusterSecretValue` parameter MUST be identical
- the `DefaultTenantID` parameter MUST be identical
- the `Domain` parameter MUST be identical
- skip the `ServicesDNSZone` parameter
- refer to outputs of the `1-1-vpc` template for the `Peer*` parameters

### Step 4: peer networks

Deploy the template `6-1-vpc-peering` where you had deployed the Join Server to create the peering association. Refer to the Crypto Server stack for inputs of `6-1-vpc-peering`.

{{% tts %}} uses Route53 for service discovery. In order to enable the Join Server <-> Crypto Server service discovery, you need to associate the `${Cluster}.${Environment}.${NetworkName}.cluster.local` hosted zone of Join Server with the VPC of Crypto Server and vice versa. For details on how to do it, refer to the [AWS Documentation](https://aws.amazon.com/premiumsupport/knowledge-center/route53-private-hosted-zone/).

### Step 5: activate the connection

Go through the Join Server stack and turn on switches for Interop:
- `3-2-load-balancer`: set `InteropEnabled` to value other than `disabled`
- `5-3a-ecs-is-service`: since you're setting up Join Server, not Identity Server interoperability, leave `InteropEnabledIS` as `disabled`
- `5-4-ecs-services`:
- set `InteropEnabledJS` to the same value as previously `InteropEnabled`
- set `CryptoServerDNSName` to `cs.${Cluster}.${Environment}.${NetworkName}.cluster.local`, where values for tuple `${Cluster}.${Environment}.${NetworkName}` come from the Crypto Server deployment
- set `InteropPacketBrokerEnabled` to a value relevant for your use case
- set `InteropPacketBrokerTokenIssuer` to a value relevant for your use case

Set the `ServicesDNSZone` parameter in the Crypto Server deployment. The value is expected to be in format `${Cluster}.${Environment}.${NetworkName}.cluster.local`, where where values for the tuple `${Cluster}.${Environment}.${NetworkName}` come from the Join Server deployment

### Step 6: encrypt key values for secure element parts

Go to the Crypto Server stack and encrypt secure element parts keys using the AWS KMS key from stack output `MicrochipPartsTableKey` and put them into the DynamoDB table from output `MicrochipPartsTable`. You can do it in the following way:
1. Create a file `input.json` with the following contents: `{"KeyId": "<MicrochipPartsTableKey>", "Plaintext": "<SecureElementKeyValueInBase64>"}`
2. `aws kms encrypt --cli-input-json file://input.json`. It is important to use the input json file so that secret value doesn't stay in history
3. Change the contents of `input.json` to `{"TableName": "<MicrochipPartsTableKey>", "Item": {"part_number": {"S": "<PartNumber>"}, "psk_root_keys_encrypted": {"S": "<CiphertextBlobFromKMSEncrypt>"}}}`
4. `aws dynamodb put-item --cli-input-json file://input.json`
5. `rm input.json`

At this point interoperability should be working. It might be required to restart the containers before they pick up new configuration.

# Identity Server interoperability

As opposed to Join Server interoperability, this setup doesn't expect you to use two accounts.

### Step 1: deploy {{% tts %}}

Deploy {{% tts %}} normally.

### Step 2: add certificates

For the server certificate:
- if you use `server-only-authentication`, AWS Load Balancer will use existing certificate
- if you use `mutual-authentication`, then use the certbot task to request a certificate. It will be put into AWS Secrets Manager, and then fetched by {{% tts %}}

If you're using `mutual-authentication`, you need to populate client certificates stored in the interop config bucket, created in template `2-4b-routing-s3`. In this bucket you need to place a file `config.yml`, which contains a map `<net-id>: <ca.pem>`. Example configuration:

```yaml
000000: ca-000000.pem
000009: ca-000009.pem
```

### Step 3: activate the connection

Go through the stack and turn on switches for Interop:
- `3-2-load-balancer`: set `InteropEnabled` to value other than `disabled`
- `5-3a-ecs-is-service`: set `InteropEnabledIS` to the same value as previously `InteropEnabled`
- `5-4-ecs-services`:
- leave `InteropEnabledJS` as `disabled`, since you're setting up Identity Server, not Join Server interoperability
- leave `CryptoServerDNSName` empty, as Crypto Server is not used by the Identity Server
- set `InteropPacketBrokerEnabled` to a value relevant for your use case
- set `InteropPacketBrokerTokenIssuer` to a value relevant for your use case
In template `4-2a-configuration`, configure whether Packet Broker can authenticate with your {{% tts %}} deployment via LoRaWAN Backend Interfaces with the `InteropPacketBrokerEnabled` setting. Packet Broker uses this to request whether your {{% tts %}} deployment is the home network of an end device that is trying to join a network.
8 changes: 6 additions & 2 deletions doc/content/reference/configuration/the-things-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ $ echo "AzYFASd/Gcggs..." | base64 --decode > ttn-lw-stack-license.bin

{{< distributions "Cloud" "Enterprise" >}} The key vault is used to store secrets, such as TLS certificates and the keys for encrypting LoRaWAN root keys in the database. These secrets can also be cached. {{% tts %}} supports keys stored in AWS Secrets Manager, or static configuration for development purposes.

- `key-vault.provider`: Provider (static or aws)
- `key-vault.provider`: Provider (`static` or `aws`)
- `key-vault.static`: Static key encryption keys; values use hex encoding
- `key-vault.cache.size`: Cache size (caching is disabled if size is 0)
- `key-vault.cache.ttl`: TTL for cached elements (no expiration mechanism is used if TTL is 0)

- `key-vault.aws.region`: AWS region
- `key-vault.aws.secret-id-prefix`: Secret ID prefix
- `key-vault.aws.client-certificate-secret-label`: Secret label for loading the client certificate from Secrets Manager. Conflicts with `key-vault.aws.certificate-authority-arn`. If set, the secret must contain a `certificate` and `key` field with PEM encoded values {{< new-in-version "3.24.0" >}}
- `key-vault.aws.certificate-authority-arn`: Certificate authority ARN for issuing the client certificate from AWS Private CA. Conflicts with `key-vault.aws.client-certificate-secret-label`. If set, the client certificate is issued from AWS Private CA {{< new-in-version "3.24.0" >}}
- `key-vault.aws.client-certificate-common-name`: Common Name for issuing the client certificate from AWS Private CA. If not set, the host name is used {{< new-in-version "3.24.0" >}}

## TLS Options

Expand Down Expand Up @@ -376,4 +380,4 @@ Tenants can have custom configuration, such as custom branding or custom user re

{{% tts %}} supports experimental features.

- `experimental.features`: Experimental features to activate
- `experimental.features`: Experimental features to activate
28 changes: 15 additions & 13 deletions doc/content/reference/interop-repository/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The Join Server configuration provides means to configure how the components int
scheme: 'https' # URL scheme. Defaults to https
fqdn: 'thethings.example' # FQDN of the Join Server
port: 443 # port to connect at. Defaults to 443
protocol: 'BI1.0' # Backend Interfaces protocol to use (one of BI1.0 or BI1.1)
protocol: 'BI1.0' # Backend Interfaces protocol to use (BI1.0 or BI1.1)
paths: # custom URI paths to use for various requests. Defaults to /
join: 'some/path' # the URI path to use for JoinReq
rejoin: 'some/other/path' # the URI path to use for RejoinReq
Expand All @@ -47,13 +47,16 @@ basic-auth: # HTTP Basic Authentication (optional)
username: 'user' # HTTP Basic username
password: 'secret' # HTTP Basic password
tls: # TLS configuration to use (optional)
root-ca: 'path/to/clientca.pem' # path to client CA
certificate: 'path/to/clientcert.pem' # path to client TLS certificate
key: 'path/to/clientkey.pem' # path to client TLS key
source: 'file' # TLS client certificate source (file or key-vault)
root-ca: 'path/to/clientca.pem' # path to CA file to verify TLS server certificate (optional)
certificate: 'path/to/clientcert.pem' # path to TLS client certificate
key: 'path/to/clientkey.pem' # path to TLS client key
headers: # HTTP headers to send, defined as key-value map
Some-Header: 'SomeValue'
```

If `tls.source` is set to `key-vault`, {{% tts %}} uses its [Key Vault]({{< ref "/reference/configuration/the-things-stack#key-vault" >}}) configuration to load the TLS client certificate.

## Interoperability with The Things Join Server

The Things Join Server is a stand-alone LoRaWAN Join Server that can be deployed by device makers, distributors and integrators.
Expand Down Expand Up @@ -81,18 +84,16 @@ join-servers:
fqdn: 'join.cloud.thethings.industries'
protocol: 'BI1.1'
sender-ns-id: 'ABCDEF0000000001'
basic-auth:
username: 'ABCDEF0000000001'
password: 'secret'
tls:
source: 'key-vault'
```

```yml
# tti/as-js.yml
fqdn: 'join.cloud.thethings.industries'
protocol: 'BI1.1'
basic-auth:
username: 'thethings.example.com'
password: 'secret'
tls:
source: 'key-vault'
```

## Interoperability with Semtech Join Server
Expand All @@ -119,7 +120,8 @@ protocol: 'BI1.0'
paths:
join: 'api/v1/rens/rens-1::2/lbi_joinreq' # replace 'rens-1::1' by the RENS issued by Semtech
tls:
root-ca: './ca.pem' # path to the client CA issued by Semtech
certificate: './cert.pem' # path to the client TLS certificate issued by Semtech
key: './key.pem' # path to the client TLS key issued by Semtech
source: 'file'
root-ca: './ca.pem' # path to the CA issued by Semtech
certificate: './cert.pem' # path to the TLS client certificate issued by Semtech
key: './key.pem' # path to the TLS client key issued by Semtech
```

0 comments on commit a425880

Please sign in to comment.