IDS Connector deployment

This guide describes how to deploy an IDS connector used by SCSN Service Providers to expose back-end services to the network.

Requesting an Identity

Identities for the test environment can be requested via: Test DAPS. Both a participant certificate and a component certificate should be requested, by creating a Certificate Signing Request. The exact formatting of the certificate content is not fixed for the test environment, for the production environment guidelines will be made available. Contact Mike de Roode for the approval of the certificates.

Certificate Signing Requests can be created using several different tools, e.g. OpenSSL, make sure the key-length is at least 2048 bits. The distinguished name should reflect your participant and connector respectively, however, the these certificates are not used for SSL transport encryption but are only used for authentication and therefore the Common Name of the certificate is not required to be a Fully Qualified Domain Name.

The Participant ID and Connector ID are IDS identifier that can be chosen freely for the test environment, but they should be uniform resource identifiers (URI). This can be either an URL (starting with https://) or an URN (starting with urn:), since these identifiers are only used for identification the URN scheme is preferred. A common URN prefix of urn:scsn:ids could be used (not registered as official prefix):

  • Participants: urn:scsn:ids:participants:Service_Provider_Name

  • Connectors: urn:scsn:ids:connectors:Service_Provider_Connector_Name

The result of the interaction with the DAPS should result in having at least the following files:

  • participant.key

  • participant.cert

  • connector.key

  • connector.cert

  • config.yaml (only required for alternative Docker Compose deployment, but useful as reference for Kubernetes deployment)

  • docker-compose.yaml (only required for alternative Docker Compose deployment)

Deploying the Connector

The deployment scenario for the IDS connector uses Kubernetes and Helm. Make sure you are familiar with setting up an Kubernetes cluster and know what the purpose of at least the following resource types are: Deployment, Pod, Stateful Set, Service, Ingress, Ingress Controller, Config Map.

Helm is used as package manager for Kubernetes, in the form of an Helm chart. This Helm chart uses templates to describe resources and makes sure that the right connections are made between the different resources of the complete deployment. From a deployment perspective only the values.yaml file must be modified to reflect your configuration.

The Helm chart can be found on the Connector documentation page, with comments inside the values.yaml indicating the configuration options for the Helm chart.

Ingress Controller

For Kubernets Ingresses to work, the cluster should be configured correctly with an existing Ingress Controller (see Ingress Controllers). The ingress controller should be an nginx ingress controller, or another controller that supports the same annotations. Other ingress controllers may work, but can require manual annotations to be configured.

Depending on the cluster, optionally, automatic certificate renewal can be supported via LetsEncrypt and cert-manager. For this the manual for Microsoft Azure can be followed, except the step for setting up the DNS A record this manual is provider independent. Remember the identifier of the ClusterIssuer, since this is needed in the configuration.

Services

The services that will be deployed are the main focus for exposing the connector to other connectors and for exposing the connector to the internal backend systems.

  • service/{{ .Release.Name }}: The /router endpoint of the http (8080) port of this service should be exposed via an Ingress to the outside world. This will be used by other connectors to exchange messages. This service also exposes a metrics (9999) port, that returns metrics of the connector via its root. As well as an core container API on the api (4567) port, this will be enhanced in the future and documented accordingly.

  • service/{{ .Release.Name }}-openapi-data-app: The /openapi endpoint of this service must be used by your backend system in order to send messages to other connectors. This endpoint acts as kind of a proxy for the messages. Note: this service is not protected, exposing this to the outside world allows everyone to send messages via your connector! . Exposing this service can be done in several ways:

    • ClusterIP service, when the backend system also is deployed on the same Kubernetes cluster

    • NodePort service, when the cluster nodes are not directly reachable from outside your network a NodePort service can be used which allows other non-kubernetes machines to access the service via a specific port on one of the cluster nodes.

    • Ingress, a ingress can be used with strict access control policies (using authentication or strict network segregation).

Core container service configuration

The core container is intended to be exposed via an ingress, this can be configured in the coreContainer.ingress values. For this an ingress controller is required to be available on the kubernetes cluster. The chart assumes an nginx ingress controller, since there is a need for the configurability of the ingress. Optionally, a cert-manager ClusterIssuer can be used to automatically provision LetsEncrypt (or other ACME certificate authorities) certificates for the ingress.

The host or coreContainer.host value is used as hostname for the ingress.

For the core container, the rewriteTarget must be able to include /router, this endpoint should be publicly accessible. Examples configurations are (with the corresponding accessUrl):

path: /(.*)
rewriteTarget: /$1
# accessUrl -> https://HOST/router
---
path: /connector(/|$)(.*)
rewriteTarget: /$2
# accessUrl -> https://HOST/connector/router

The ingress configuration is mapped to the http port of the core container, next to this port there are two other ports exposed as services:

  • api: exposes the core container API, used for the optional user interface of the connector. Will be explained via an OpenAPI description in the future, together with a new user interface

  • metrics: exposes metrics of the core container via a Prometheus style exporter

OpenAPI data app service configuration

For the OpenAPI data app service there is more flexibility, however, it is important that this service is not exposed publicly without strict firewalling or authentication.

Per default, the OpenAPI data app service is exposed as ClusterIP service, which makes the data app only available within the kubernetes cluster.

A NodePort service can also be configured, this service exposes the service to a port above 30000 on the nodes of the cluster. It is important to ensure firewall restrictions to make sure only your own applications can use this port. The following example exposes the data app on port 31000:

- port: 8080
  name: http
  nodePort: 31000

Also an ingress can be used, however it is advised to add authentication to the ingress (e.g. https://kubernetes.github.io/ingress-nginx/examples/auth/basic/):

- port: 8080
  name: http
  ingress:
    path: /(.*)
    rewriteTarget: /$1
    clusterIssuer: letsencrypt
    annotations:
      nginx.ingress.kubernetes.io/auth-type: basic
      nginx.ingress.kubernetes.io/auth-secret: basic-auth
      nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'

Distributed tracing

Both the core container as the OpenAPI data app support distributed tracing, respecting X-B3 headers. The trace identifiers are logged to standard out of the containers. The log templates are:

  • Core container (Log4J):

%d{ISO8601} | [%X{traceIdHex}/%X{spanIdHex}] | %-5p | %-32c{1.} | %m%n
  • OpenAPI data app (Logback):

%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}

Horizontal scaling

Both the core container as the OpenAPI data app can be scaled horizontally by setting the coreContainer.replicaCount key or in the ids.containers[0].replicaCount.replicaCount key. The distribution of the load is provided via Kubernetes.

Metrics endpoint

The core container exposes a metrics endpoint with relevant metrics of the processing times and number of messages exchanged. These metrics may be used to identify bottlenecks early, or can be used to support the decision making for horizontal autoscaling of the core container. The important keys that are exposed are: camel_exchanges_failed, camel_exchanges_inflight, camel_exchanges_completed, camel_exchanges_total, camel_last_processing_time, camel_total_processing_time.

Metrics are gathered per route, for easier searches the ids.routes[].id key can be used to specify custom route identifiers (NOTE: these identifiers must be unique in the connector). Per default the routes are given an identifier based on the order in the ids.routes list, together with the type of the route (e.g. route-0-https-out and route-1-https-in for the default routes in values.yaml).

In following releases this functionality will be extended with metrics for the OpenAPI data app and with sample deployments for Prometheus and Grafana (to visualize the metrics) and the Kubernetes Horizontal Pod Autoscaler in combination with these metrics.

Configuration

The configuration of the Helm deployment is contained in the values.yaml file. The values.yaml file in the Connector documentation page contains a basic deployment, with a lot of comments to further structure your connector. An example of a full values.yaml file can be found in values.test.yaml which includes ingress configuration, an agent database, and demo applications as used by the TNO Test Buyer. The contents of the values.yaml file is also shown below for easy reference by line numbers:

Values.yaml
pullSecret:
  # pullSecret.name -- the name of the pullsecret
  name: pull-secret
  # pullSecret.credentials -- (optional) the credentials to be used to connect to a specific Docker registry, when specified the secret will be created
  # @default -- {}
  credentials:
    # pullSecret.credentials.registry -- the hostname of the Docker registry
    registry: registry.ids.smart-connected.nl
    # pullSecret.credentials.username -- the username of the Docker registry
    username: scsn
    # pullSecret.credentials.password -- the password of the Docker registry
    password: tnoidsscsn

# host -- Host url for the connector used in optional ingress specifications
host: domain.name

# mongodb -- MongoDB dependency values, read the [docs](https://hub.helm.sh/charts/bitnami/mongodb/7.14.5)
mongodb:
  # mongodb.enabled -- whether you want to deploy a mongodb chart 
  enabled: false
  # mongodb.mongodbRootPassword -- Password for the root user, which is used to connect to the MongoDB instance.
  mongodbRootPassword: 
  replicaSet:
    # mongodb.replicaSet.enabled -- configure MongoDB as replicaset
    enabled: true
  persistence:
    # mongodb.persistence.size -- size of the persistent volume
    size: 1Gi


deployment:
  # deployment.pullPolicy -- Pull policy for all data apps configured in `values`
  pullPolicy: Always
  # deployment.annotations -- Annotations applied to the core-container and data apps configured in ids.containers
  annotations: {}

coreContainer:
  # coreContainer.replicaCount -- Replicas as specified in the core container deployment
  replicaCount: 1
  # coreContainer.image -- Core Container docker image name
  image: registry.ids.smart-connected.nl/trusted-core-container:master-nonroot
  # coreContainer.ingress -- Ingress configuration for the core container
  # @default -- {}
  # ingress:
  #   # coreContainer.ingress.host -- Hostname used for this ingress, defaults to the top-level host
  #   # host: 
  #   # coreContainer.ingress.path -- External path to be used by the ingress (can contain regular expressions)
  #   path: /(.*)
  #   # coreContainer.ingress.rewriteTarget -- Rewrite rule used for internal messaging, messages must be able to arrive at the /router endpoint
  #   rewriteTarget: /$1
  #   # coreContainer.ingress.clusterIssuer -- Optional clusterIssuer reference for usage in combination with cert-manager
  #   clusterIssuer: letsencrypt
  # coreContainer.securityContext -- Security context for the container
  securityContext:
    runAsUser: 999
    runAsGroup: 999

# services -- Data app service configuration, per default only ClusterIP services are created for port 8080. Custom configuration for ClusterIP, NodePort, or Ingress can be created. See the Readme for more info.
services: {}
  # openapi-data-app:
  #   ports:
  #     # ClusterIP
  #     - port: 8080
  #       name: http
  #     # NodePort
  #     - port: 8080
  #       name: http
  #       nodePort: 31000
  #     # Ingress
  #     - port: 8080
  #       name: http
  #       ingress:
  #         path: /(.*)
  #         rewriteTarget: /$1
  #         clusterIssuer: letsencrypt
  #         annotations:
  #           nginx.ingress.kubernetes.io/auth-type: basic
  #           nginx.ingress.kubernetes.io/auth-secret: basic-auth
  #           nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'

ids:
  # ids.logLevel -- Log level for the core container 
  logLevel: INFO
  tpm:
    # ids.tpm.createSimulator -- Whether you want to simulate a Trusted Platform Module
    createSimulator: false
  
  info:
    # ids.info.idsid -- IDS Component ID, as specified in the DAPS component registration
    idsid: &idsid urn:scsn:ids:connectors:...
    # ids.info.titles -- Title(s) of the Connector as used in the Broker, parsed as language string
    titles:
      - Connector Title
    # ids.info.descriptions -- Description(s) of the Connector as used in the Broker, parsed as language string
    descriptions:
      - Connector Description
    # ids.info.accessUrl -- Access URL used for external connectors to access the connector
    accessUrl: https://HOST/router
    # ids.info.curator -- Curator of the Connector, i.e. the participant that is the owner of connector
    curator: &participant urn:scsn:ids:participants:...
    # ids.info.maintainer -- Maintainer of the Connector, i.e. the participant that is technical maintainer
    maintainer: *participant
    # ids.info.broker -- Broker configuration
    broker:
      id: urn:ids:connectors:Broker
      address: https://broker.test.ids.smart-connected.nl/infrastructure
      autoRegister: true
      profile: FULL
  keystore:
    # ids.keystore.key -- Base64 encoded PKCS#8 component private key
    key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
    # ids.keystore.cert -- Base64 encoded PEM component certificate
    cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...

  truststore:
    # ids.truststore.chain -- IDS Trusted Certificate Authority Chain
    # @default -- LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
    chain: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lVUVJGL2VNZmpNdEpBNU93d1ViNytLMHVia2RRd0RRWUpLb1pJaHZjTkFRRUYKQlFBd05qRUxNQWtHQTFVRUJoTUNUa3d4RERBS0JnTlZCQW9NQTFST1R6RVpNQmNHQTFVRUF3d1FTVVJUSUZSRgpVMVFnVWs5UFZDQkRRVEFlRncweE9UQTVNamN4TXpFd01UZGFGdzB6T1RBNU1qSXhNekV3TVRkYU1EWXhDekFKCkJnTlZCQVlUQWs1TU1Rd3dDZ1lEVlFRS0RBTlVUazh4R1RBWEJnTlZCQU1NRUVsRVV5QlVSVk5VSUZKUFQxUWcKUTBFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNYYTRVVWFkRE53dVRXSUJLTgpIalgxbXJQMjczR1ZhSXVVN0RvMWtadzRINDdRQS9wODFMQmF4T0J0aWlQRkgram4xT3N0aUtFQm5PaVRwNTJwCnUwSTVJWTZMb015TVFwaHJpaDArYWczQ2RCLzhVNUJIOUFDcGFHeEorTkZjT1ZvZkxWd1ZwU1BnWDRab0dDWmoKUENwdW1FV0dIVU5HWXc4cFBmWGIzcU85bVFVQU1ZZFRhYUtYQW1ZVUp1WTR2L3A5ZUVxYTZnclVDdHE0Nm9LMgpZaUpORVA3RmJsOXJvL3lKdDRRY0YzSUg2VDRGUXFzUnN5M3VHeVdURkJJY2FLK25rdWRVSTljSzZaUUR1UXl0Ck96cHVtak9JQmdFVWErSSt6RVNzdWs1QTQ4eW12L1pwTVRtOWFkb2xnMVlyejZ5em5rYVlxRjcvdkc1WUkrWWcKSldqaEFnTUJBQUdqZnpCOU1CMEdBMVVkRGdRV0JCUUdwWHFFK2pMeWJJL3c3MWpZbFNUSkhVQlR2akFmQmdOVgpIU01FR0RBV2dCUUdwWHFFK2pMeWJJL3c3MWpZbFNUSkhVQlR2akFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQXNHCkExVWREd1FFQXdJQlJqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUhBd0l3RFFZSktvWkkKaHZjTkFRRUZCUUFEZ2dFQkFISjdpTnV0c3YyVU55K0Y1blBHRmFERksxT2l2akEvcjJxNGtrM3FVVy9IRkFLVgpBT0l3dEZyamU0RDlpWVpQTWlkN05JT1Bhd2l6UExBUDFMayt2ck9rTE41NlNXT3UwbmZEYkY5a1V2b3QrU0tzCmpqNHZmb2lVRUsrMUhHQnUvdzhaWjFnU21WdmNxSjZ2a2FHZThwM1VlbkRybnFyTFluZEFvcThzUXdVK0V2b0EKemdFcWNsdTN0cWF5Tk1PbzdPUmVINXNYVjR1OUFZZWhZc3Z2OUhqS0hVY1VMc2k2K0FMNVJaVDJKNUI2c0pQYwpjcXRhUndzWFh5MzBkYS9CYXVaS2Z1NVpjZEFsL2haMWRpR1MyT1lIbzRDSVBudkdOK3hnaUxENWczbGczNWdCCmhxT051MklPN0tJeTZYQ2ljLzlRNnAvbjRaRTVIRktWeDRIeDkvRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUViRENDQTFTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRVUZBREEyTVFzd0NRWURWUVFHRXdKT1RERU0KTUFvR0ExVUVDZ3dEVkU1UE1Sa3dGd1lEVlFRRERCQkpSRk1nVkVWVFZDQlNUMDlVSUVOQk1CNFhEVEU1TURreQpOekV6TVRBeE4xb1hEVEkzTURreU5URXpNVEF4TjFvd1BERUxNQWtHQTFVRUJoTUNUa3d4RERBS0JnTlZCQW9NCkExUk9UekVmTUIwR0ExVUVBd3dXU1VSVElGUkZVMVFnUkVWV1NVTkZJRk5WUWlCRFFUQ0NBaUl3RFFZSktvWkkKaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFOSlNzZEtHeUlReEUvblJlRTFJQytyMVlyeEh4NmE5U2g1dwoxUjNFdU5LdDE3amkrbTVQMWFBS3lzR1hTWVp2MlZMcU0xZTkzYTFsbFJKbzRQRyttSEo0OWxLY3FZdXprakM2Cjc0V2U4cGd2bGZ5aFltMGdFMkhhaHBrd0VGVEgyRUJxKy9VZURKbndBVUkrT3U0aTlFeWFiOElIa1pYNWlHZ2QKWFB5dDZ5blRlSGxjOVFMNXRROEF0VGlHcWlFdTlrb2VjYmVTQ0dVSjkyRVcvTFVkQnZoVG9jbjNLZ1F1MzNCaworb3V1Q2drU2lEMWVFelRnakdmcGtteXlJR1NnbjZDSFd6dlRmWDJkOWQ2Z05aS2VsMTV4elc2ZGlNOE1FQ21GClJrUEdzUnJoemNaRDdYS3I0Mk4zSlVrMXJhMk82eWhRTkQxd3NZZkN0WEhlTW9LZ0F1Y0tDdDYvUzY1L1laMW8KM2F1U2RKRS9tMHRnazdkaWpoais1M3Z2RVZma3dJcEdTQTFFeExKT3p0RXRWNG5pSlhsUUVRa2xtdWxhaVB2Rwoxak01QWMxM0ZqZkFqZ3NDY2dCT1M3bVdIc0h4bjM3c0dSQWQxYkZvYW1keE1zYmVFSzFYNnNINFJzYW5oUHMzCk5pcFd2YlRFS2gzSS9XMTdWOGZyUmNlUC9TMXY0SUoxUWszTFV1MGx5Rk1Rc0JWN0lrcnZBRVdkREhRZ3psaXgKNUpjMktsbklDSjRmWTg3WFVwMnhZSTdEY3RBQTRKU1BxR1hKalV0MGhsdXJXT0ZMOWJvUUYwd3RBS3M0N1ZkNgpHd1phTHNjSVdSRDAxNEFlc242SUpxU2c5MmtmWlpiamFqbXo4VUlUWktyQkJ0bUltYjQ4aFFFYmZrN3Rka2d1CitQZzc2aVpGQWdNQkFBR2pmekI5TUIwR0ExVWREZ1FXQkJRSFh0VFBHenVMY3k0bzF5eHMwZStHd2U5K0RqQWYKQmdOVkhTTUVHREFXZ0JRR3BYcUUrakx5YkkvdzcxallsU1RKSFVCVHZqQVBCZ05WSFJNQkFmOEVCVEFEQVFILwpNQXNHQTFVZER3UUVBd0lCaGpBZEJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdEUVlKCktvWklodmNOQVFFRkJRQURnZ0VCQUFkWGlsM3BFNjE5NHZHa3cvUXlkSy9PZlhyNG9WdlhIWENoRTZaOEg1djEKZit3aU5vb3liQWVLWVpwQlhTbHAvT3J4UTlQY081UG9qQjBSbUNDOTQ1V0VMdzFuLzBNajJpMU1ZdFNGR0QxVwpwL1Q4YVg5RUh5Q2w5SUJDSnlUODU2bnZjRkNnZkR6K0Q2T2d4V3Fsc0pBdTlLZTgzVHpRWElKNXlUL0RXUmRsClk5bnFqY3JMamJLSU1ubGdHSWVkZzJJaHZSSW9hRjc3ZHRIYWM0S1J0dlYvWml2RVJoNElHdzdiRWt0bnFqQngKd1VUSnNzU0xhY2FrUXBINkhsSHdOK0xLSFRIT0ZQWTAwcFVEbzRjRWsvcGp5eFZ1T1lLb1g3bU1TTTFyWnRSegozUHlmSXVJNXhLaENPWXA4OUVCZ0FTWDJPN2VHMWExa2luTE9Cd1hNVVFFPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCgotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRWNUQ0NBMW1nQXdJQkFnSUJBVEFOQmdrcWhraUc5dzBCQVFVRkFEQTJNUXN3Q1FZRFZRUUdFd0pPVERFTQpNQW9HQTFVRUNnd0RWRTVQTVJrd0Z3WURWUVFEREJCSlJGTWdWRVZUVkNCU1QwOVVJRU5CTUI0WERURTVNRGt5Ck56RXpNVEF4TjFvWERUSTNNRGt5TlRFek1UQXhOMW93UVRFTE1Ba0dBMVVFQmhNQ1Rrd3hEREFLQmdOVkJBb00KQTFST1R6RWtNQ0lHQTFVRUF3d2JTVVJUSUZSRlUxUWdVRUZTVkVsRFNWQkJUbFFnVTFWQ0lFTkJNSUlDSWpBTgpCZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUEwZHVOOVo1a1NWRG9jSkZDTVJSYnFuSlFWV3FxCkZPbDh3aE9ZZ3llMk5RNmg3citRcm95SUM2eW9JUjhyeitQOURvamFKTVlkOGJVWm5mRm9kSUtYUGNmTjJ5VjIKOFpkRzMvTzFZcjhxb29EaS8rMnBER1FZbk9GMGxxL3B6cXAybWRsbHo1aDQ1VDZhTUk5MzhNYjBIb01RU2tkaQpDOGVyKzRNTEwrelVmdHNLNnFwUlFmRVkyREZWKytQOXNOMzd2RnYyK0x6MlBYNUJCSG45a0lJSjBoV1BmZ3BzCmtPZXdIcHBHejRKUnZYK29BbGFvNGlYbUY1bEo3MlZIdW9BQ0Vzd2VUejFvclhvbVZTR2c5SXJ4S3kyczdPVk0KbHV3a0ZaaGhOeUdkNmc4SU95UFJoQU1GK3Y3ZU9rczVxOGJobkdBdThucjQyNnN5TFFWeGVYVjV3N1grQmErRgplbEFSVmQ3Yyt4dUFCUXplMkZ6cGdIVEJvRXlXWm15cnpsUm9VV1dNU2NsT0J4cXZmTERFM0t6UVRjQVluQTBMClJvMUhEdGF6bUFKZUlsZTNYdVlDSDhYcHBydnFPMWZ6MjBEYThsN0cyZ1FyZ3p1aDVPRmhrQlpPNHhpL1J4eDMKMGVRek5FYjdWQ2NSUDRwK1FTZzd1NFdPVkNSVGYyTE9HRHlSczk1M3djVUJ2RU5YNWhLN3hpRmhUZnJuT2ExTwo5dEp1OEdqWGxzWmxIQ2ZCQmliOThhQ0U5VW1tVU4wd3d2WnMvYUxmSTZablZiTDhSUzRCYXBNL2RVcmlhdVUrCjl6YXlMQytNNnJuSEpJc0h1cWI3S3hMaXVWbENXMi9mRTJBNHN2b2RhWStFS21kaXo3cWRVUU5YK2JLeHVkTlIKMmE5YVE4YUFyMzVPdTM4Q0F3RUFBYU4vTUgwd0hRWURWUjBPQkJZRUZHM2tHN1JyeVpkQ2czdEZWK2dra1pkVApJVmFWTUI4R0ExVWRJd1FZTUJhQUZBYWxlb1Q2TXZKc2ovRHZXTmlWSk1rZFFGTytNQThHQTFVZEV3RUIvd1FGCk1BTUJBZjh3Q3dZRFZSMFBCQVFEQWdHR01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0QKQWpBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWc1bFRQK2tjcEMxalpkbFE5SlMwSzNHQnVlcGk0SzdKckh3ZwpHSmhvbkIwT1lSb2EvalFVSHo2Q05iLzAzY1NFK3FkL2JnazhzcFhwTlhJSjlvSDgwWVlnM3N6MmVtVVgrcTN0CmNERFNldDBBRC9sM0lxT3VuZ0JRaFlqc2pqRHJDUThqT21GalE3Sm9abGFrZG54TkJ1R2hTMmw1NjZPSUlLK0QKM1BkTkd1ZHBGOEE2cUVobGJPWm52UUM3WWxaOTNPZDNUUHhpdFMzSTA2azh3ZktpcjJlKzdOREpRVTNUZkRVRgo5QU0yRkJzc2c2M2FERWFPL1FJWGNWN2tyUWdzRll1aFY1T01QYXEzMnZMUVFIUllkbzJFSWhKcnhua2h0dmdOCnpPb1lzcW80eEJVN0N0V0RRVDY2NHhGck5CQkpzbFc2ZlVQa0NLa1RqcXhhR0N0SGhRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=

  # ids.containerManager -- Needed for kubernetes deployment
  containerManager:
    type: none

  daps:
    # ids.daps.url -- DAPS url
    url: https://daps.test.ids.smart-connected.nl
    # ids.daps.uuid -- UUID as received when registered at a component in the DAPS
    uuid: 00000000-0000-0000-0000-000000000000

  # ids.routes -- Core container internal message router (Apache Camel) configuration
  routes:
    # ids.routes[0] -- Default egress route for the Core Container
    - type: HTTPS-out
      clearing: false
    # ids.routes[1] -- Default ingress route inside the Core Container, used to expose the message router for external use (e.g. as backend for the Kubernetes Ingress)
    - type: HTTPS-in
      router: 'http://{{ template "connector.fullname" $ }}-openapi-data-app:8080/router'
      endpoint: router
      clearing: false

  # ids.containers -- Additional containers (Data Apps or helpers) to deploy 
  # @default -- []
  containers:
    # ids.containers[0].type -- Type of the container, either data-app or helper
  - type: data-app
    # ids.containers[0].image -- Docker image used for this container deployment
    image: registry.ids.smart-connected.nl/openapi-data-app:master
    # ids.containers[0].name -- Name of the container, will be prefixed in the actual Kubernetes deployment
    name: openapi-data-app
    # ids.containers[0].name -- Number of replicas 
    replicaCount: 1
    # ids.containers[0].securityContext -- Security context for the container 
    securityContext:
      runAsUser: 999
      runAsGroup: 999
    config:
      # ids.containers[0].config.id -- IDS Component ID, as specified in the DAPS component registration
      # Should be identical to ids.info.idsid, and is, therefore, an YAML anchor
      id: *idsid
      # ids.containers[0].config.participant -- Curator of the Connector, i.e. the participant that is the owner of connector, as specified in the DAPS participant registration
      # Should be identical to ids.info.curator, and is, therefore, an YAML anchor
      participant: *participant
      selfDescriptionConfig:
        # ids.containers[0].config.selfDescriptionConfig.type -- Self Description configuration, used for Broker registrations
        type: openapi
      # Custom Properties for the OpenAPI Data App
      customProperties:
        # ids.containers[0].config.customProperties.consumerOnly -- (OpenAPI Data App) Act as consumer only, so no agents have to be specified
        consumerOnly: false
        # ids.containers[0].config.customProperties.openApiBaseUrl -- (OpenAPI Data App) Base URL for the OpenAPI specifications used
        openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
        # ids.containers[0].config.customProperties.backEndBaseUrl -- (OpenAPI Data App) (Optional) Global backend URL, agent IDs will be forwarded to {backEndBaseUrl}/{agentId}/{version}
        # backEndBaseUrl: # http://globalbackend:8080
        # ids.containers[0].config.customProperties.backEndBaseUrlMapping -- (OpenAPI Data App) (Optional) Global backend per version, agent IDs will be forwarded to the respective mapping with agent ID postfixed
        # backEndBaseUrlMapping:
        #   # 0.4.6: http://046.gloablbackend:8080
        # ids.containers[0].config.customProperties.versions -- (OpenAPI Data App) Versions of the OpenAPI specification to be used
        versions:
        - 0.4.6
        # ids.containers[0].config.customProperties.agentDatabase -- (OpenAPI Data App) Agent database configuration for dynamic agent configuration
        # @default -- {}
        # agentDatabase:
        #   # ids.containers[0].config.customProperties.agentDatabase.hostname -- (OpenAPI Data App) MongoDB hostname
        #   hostname: "{{ $.Release.Name }}-mongodb"
        #   # ids.containers[0].config.customProperties.agentDatabase.port -- (OpenAPI Data App) MongoDB port
        #   port: 27017
        #   # ids.containers[0].config.customProperties.agentDatabase.authenticationDatabase -- (OpenAPI Data App) Authentication database for the MongoDB deployment, in normal deployments defaults to `admin`
        #   authenticationDatabase: admin
        #   # ids.containers[0].config.customProperties.agentDatabase.database -- (OpenAPI Data App) MongoDB database to use for the agent configurations (will be created when not exists)
        #   database: connector
        #   # ids.containers[0].config.customProperties.agentDatabase.collection -- (OpenAPI Data App) MongoDB collection to use for the agent configuration (will be created when not exists)
        #   collection: agents
        #   # ids.containers[0].config.customProperties.agentDatabase.sslEnabled -- (OpenAPI Data App) Use SSL for connecting to MongoDB
        #   sslEnabled: false
        #   # ids.containers[0].config.customProperties.agentDatabase.watchable -- (OpenAPI Data App) Connect to MongoDB via watchable construct, only supported in MongoDB configured with replicaset
        #   watchable: true
        #   # ids.containers[0].config.customProperties.agentDatabase.username -- (OpenAPI Data App) MongoDB username
        #   username: root
        #   # ids.containers[0].config.customProperties.agentDatabase.password -- (OpenAPI Data App) MongoDB password
        #   password: "password"   
        # ids.containers[0].config.customProperties.agents -- (OpenAPI Data App) Agent configuration, if agentDatabase is configured and collection is empty the static configured agents are added to the agent database
        # @default -- []
        agents:
          # ids.containers[0].config.customProperties.agents[0].id -- (OpenAPI Data App) SCSN ID of the party, in the form of a GLN prefixed with `urn:scsn:`
        - id: urn:scsn:GLN
          # ids.containers[0].config.customProperties.agents[0].title -- (OpenAPI Data App) Title of the party, as will be shown in the Broker (e.g. Manufacturing Company A@en)
          title: TITLE@LANGUAGE
          # ids.containers[0].config.customProperties.agents[0].config -- (SCSN) Party information following the SCSN Party object
          # cac:PartyIdentification and cac:PartyName will be automatically filled with the previous id and title fields
          # @default -- {}
          config:
            connector:
              SUPPORTEDMESSAGES: Order 2.0;OrderResponse 2.0
            cac:PartyLegalEntity:
              cbc:CompanyID: '5674352456'
            cac:PartyTaxScheme:
              cbc:CompanyID: '456423565'
          # ids.containers[0].config.customProperties.agents[0].backendUrl -- (OpenAPI Data App) (Optional) Backend URL used for this party, will be appended with /VERSION/API_PATH (e.g. /0.4.6/order)
          backendUrl: http://internal-backend
          # ids.containers[0].config.customProperties.agents[0].versions -- (OpenAPI Data App) (Optional) Agent specific version override
          # versions: 
          # - 0.4.6
          # ids.containers[0].config.customProperties.agents[0].backendUrl -- (OpenAPI Data App) (Optional) Version based backend URL for this party
          # backEndUrlMapping:
          #   # 0.4.6: http://046.agentbackend:8080

In the next release values that have to be secret (such as the IDS identity key) will be able to be configured as Kubernetes Secrets that can be configured to be installed via the Helm chart as well as using existing secrets.

Agent Database support

The OpenAPI data app supports configuration of agents via a MongoDB database. The structure of the documents follows the static agents in the values.yaml. The chart includes an optional Bitnami MongoDB deployment, which can be activated by setting mongodb.enabled to true and set a root password. Or you can use an existing MongoDB or deploy it next to the connector.

The configuration allows for different types of MongoDB to be connected:

agentDatabase:
  hostname: "{{ $.Release.Name }}-mongodb"
  port: 27017
  authenticationDatabase: admin
  database: connector
  collection: agents
  sslEnabled: false
  # only supported with a MongoDB configured as replicaset
  watchable: true
  username: root
  password: "password" 

The provided database and collection will be created automatically if they do not exist and the user has the rights to create the database.

API version configuration

The configuration file allows different kinds of configuration for the API version that is used in the OpenAPI data app. In the example above, the global version configuration is shown that applies to all agents configured. But more granular version support per agent can also be configured, so that not all agents are required to support the same versions. Also, the way the versions are propagated to the backend system can be configured to further control the way the backends are called from the data app. The following examples show the different options, all starting on the level of the customProperties field on line 167 in the config example above.

Different versions per Agent

consumerOnly: false
openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
versions:
- 1.0.0
agents:
- id: urn:scsn:agentA
  ...
  versions:
  - 0.9.0
  backendUrl: http://agentAbackend:8080/api
- id: urn:scsn:agentB
  ...
  versions:
  - 1.1.0
  backendUrl: http://agentBbackend:8080/api

In this example, both agents support the global version(s) from the list on line 3 and specific versions specified on lines 8 and 13 for respectively agent A and agent B. So agent A will support versions 0.9.0 and 1.0.0 and agent B 1.0.0 and 1.1.0.

Internal backend configuration

Global backend URL mapping

consumerOnly: false
openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
backEndBaseUrl: http://globalBackend:8080
versions:
- 1.0.0
agents:
- id: urn:scsn:agentA
  ...
- id: urn:scsn:agentB
  ...

The global backEndBaseUrl can be used to forward all messages to generalized paths in the form of {backEndBaseUrl}/{agentId}/{version} in the example above: http://globalBackend:8080/urn:scsn:agentA/1.0.0.

Global backend per version

consumerOnly: false
openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
backEndBaseUrlMapping:
  0.9.0: http://090backend:8080
  1.0.0: http://100backend:8080
versions:
- 0.9.0
- 1.0.0
agents:
- id: urn:scsn:agentA
  ...
- id: urn:scsn:agentB
  ...

The global backEndBaseUrlMapping can be used to forward all messages for specific API versions to generalized paths in the form of {backEndBaseUrlMapping}/{agentId}/ in the example above: http://100backend:8080/urn:scsn:agentA.

Agent based generic backend

consumerOnly: false
openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
versions:
- 0.9.0
- 1.0.0
agents:
- id: urn:scsn:agentA
  backendUrl: http://agentAbackend:8080
  ...
- id: urn:scsn:agentB
  backendUrl: http://agentBbackend:8080
  ...

The agent based backendUrl can be used to forward all messages for a specific agent to a backend, this backend will be appended with the version. For example: http://agentAbackend:8080/0.9.0.

Agent based backend per version

consumerOnly: false
openApiBaseUrl: https://api-specs.ids.smart-connected.nl/specs/scsn/
versions:
- 0.9.0
- 1.0.0
agents:
- id: urn:scsn:agentA
  backEndUrlMapping:
    0.9.0: http://090agentAbackend:8080
    1.0.0: http://100agentAbackend:8080
  ...
- id: urn:scsn:agentB
  backEndUrlMapping:
    0.9.0: http://090agentBbackend:8080
    1.0.0: http://100agentBbackend:8080
  ...

This is the most configurable option, where you can specify for each agent and each version specifically what backend should be used.

Alternative deployment: Docker-Compose

This deployment method will become deprecated, since this method is less configurable and requires more manual work to safely expose the connector to the outside world.

Deploying the connector is done via docker-compose. So the environment requires to have Docker and Docker Compose installed.

First the Docker registry credentials have to be set on the machine that will deploy the connector:

docker login registry.ids.smart-connected.nl -u scsn -p tnoidsscsn

The docker-compose.yaml file is as follows:

version: '3.7'

services:
  buyer:
    image: registry.ids.smart-connected.nl/trusted-core-container:master
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./config.yaml:/ids/config.yaml:ro

The deployment is started by executing the docker compose up command inside the directory with the configuration files:

docker-compose up -d

When bringing the connector down, it is important to bring the deployment down by executing docker-compose down, as this ensures that the data app container and helper containers are shutdown correctly. Otherwise, when the machine shuts down without correctly removing these containers the possibility exists that when a new deployment is started the old containers are blocking the deployment.

Docker privileges

The basic deployment requires the core-container to mount the Docker socket, this is not desirable for all configurations. The requirement for mounting the Docker socket comes from the fact that the core container is responsible for managing the data apps. Mounting the Docker socker requires the container to run as root, or effectively as root, which might pose risks to the security of the system the container is running on.

For the docker-compose deployment you can run the core-container as non-root, but this will require the docker socket to not be mounted and to set the containerManager field in the config.yaml to none:

containerManager:
  type: none

Disabling this feature also means that the data apps should be configured and deployed manually. The image tag of the core-container must be appended with -nonroot, not including the date pinned tag, e.g.:registry.ids.smart-connected.nl/trusted-core-container:master-nonroot

Production Environment

In order to migrate to the SCSN Production environment, the following actions should be undertaken.

  1. Request a Participant and Component certificate signing request for the Production DAPS. Contact Mike de Roode for the approval of the certificates.

  2. Replace the signed certificates in the Connector's configuration.

  3. Change the UUID and truststore values:

    UUID: The UUID of your production environment certificate Truststore: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR5VENDQXJHZ0F3SUJBZ0lVUFY0T3Q3bzVhNFpDTzd6UmFPRGRxandkQ1Qwd0RRWUpLb1pJaHZjTkFRRUYKQlFBd1hqRUxNQWtHQTFVRUJoTUNUa3d4TkRBeUJnTlZCQW9NSzFOdFlYSjBJRU52Ym01bFkzUmxaQ0JUZFhCdwpiR2xsY2lCT1pYUjNiM0pySUVadmRXNWtZWFJwYjI0eEdUQVhCZ05WQkFNTUVFbEVVeUJUUTFOT0lGSlBUMVFnClEwRXdIaGNOTWpBeE1EQXhNVFEwTVRVMldoY05OREF3T1RJMk1UUTBNVFUyV2pCZU1Rc3dDUVlEVlFRR0V3Sk8KVERFME1ESUdBMVVFQ2d3clUyMWhjblFnUTI5dWJtVmpkR1ZrSUZOMWNIQnNhV1Z5SUU1bGRIZHZjbXNnUm05MQpibVJoZEdsdmJqRVpNQmNHQTFVRUF3d1FTVVJUSUZORFUwNGdVazlQVkNCRFFUQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFPOWNKVHZPTkFhMWN5dDBjU3hIZHZleHhkRFFSZEFyRGJTcHdhbGoKbWFvV2s5d3Y3RllrdU1ROW1Hd2l0NWNmNzdhb0RXYmpLVzZnTXQyNEp2ZEtKOSsvbVNCOVJab2VzSjRZVVFBeApyUHI0UXgzdjk0cndncEZWQzVOa1ZSbkkrSHJaUDlOc01kN0pIOTF4UzFpR29ScGE5Y0RCT08wNDZNNEtDR0ZVCm1QQXNpZ3loYTNNR1VFM3d3YWg3L1JzdGxwcmRLN0xHcHRJUGlNamkxMUw0S2NycTZJN2lHNGtWWHJ0eDFSK2wKSjAxb1FUVFZVZ041L2Zmdm9ISzd1UnMzb2plL212Z21QWTNnK1h3WVk4d2pXM2VLckt2NGhlQ2hLdkdwZGgvNgpveSs2UzhuYko0djFwRXR2QnFVdzR6eXZNVENpNTBObkZIZUN1YVJBREFVTlpiRUNBd0VBQWFOL01IMHdIUVlEClZSME9CQllFRk9lRzJFVnFVYkpxdG1VYU90cU9jY29tUWI0SU1COEdBMVVkSXdRWU1CYUFGT2VHMkVWcVViSnEKdG1VYU90cU9jY29tUWI0SU1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0N3WURWUjBQQkFRREFnRkdNQjBHQTFVZApKUVFXTUJRR0NDc0dBUVVGQndNQkJnZ3JCZ0VGQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFZaFd3CjRab2p0dmRORzRuaFFQOXlOcmJRYXdmMW12eW9HMzRLSDZCcWh3REhqTk1MM2llU1B0ak9LUkNDUVpMbjVhVWYKUWpmRlZXN1FWZFREL0dNSzBPVDZ4c1R0aElzbCtncGpicmx1cnc5eEJBWkpnQWM5bXVldmthdGtja0dCc2NMVwpvbGtTTFJsVjFkRVk4Z2hJV2NoMFhqY1dLV0dFWjdWdUd2eTBZWWJIYTNZSEZzYWxBejUybjVueFROdjdZTlk5CjdsVnpSZDFiRDN6dk5kVkNCTCs3M2t5dU5kSGFZbFBuZ0JJUG5DazBkZXF1Mzk1NGZpNk9NU3BEV2NxZGRQU3oKQWhWbkhCV21EOWFubXArc3Z6UVZxbEkwSDM1UlMrekZWRllHUy9IbjJkODgzU1VwQ2dwTnlwTkZZNzZBRXBKZApOVVdBdUJuelZjckV6YjVXbXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlFdkRDQ0E2U2dBd0lCQWdJQkFUQU5CZ2txaGtpRzl3MEJBUVVGQURCZU1Rc3dDUVlEVlFRR0V3Sk9UREUwCk1ESUdBMVVFQ2d3clUyMWhjblFnUTI5dWJtVmpkR1ZrSUZOMWNIQnNhV1Z5SUU1bGRIZHZjbXNnUm05MWJtUmgKZEdsdmJqRVpNQmNHQTFVRUF3d1FTVVJUSUZORFUwNGdVazlQVkNCRFFUQWVGdzB5TURFd01ERXhORFF4TlRkYQpGdzB5T0RBNU1qa3hORFF4TlRkYU1HUXhDekFKQmdOVkJBWVRBazVNTVRRd01nWURWUVFLREN0VGJXRnlkQ0JECmIyNXVaV04wWldRZ1UzVndjR3hwWlhJZ1RtVjBkMjl5YXlCR2IzVnVaR0YwYVc5dU1SOHdIUVlEVlFRRERCWkoKUkZNZ1UwTlRUaUJFUlZaSlEwVWdVMVZDSUVOQk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQwpDZ0tDQWdFQXNXdUdlYWNXT2I5VkFWVGYwanpTVXhLcUtjR3VXazM0bjByUlNZakhSMTJpUmxHb3l6UHV4S0RlCmhoaXc4eVBhSGdFOHVURTFHS1k2dnVNdHB6Q2JHRlk3WjN6QVhzSjloNDVPYnQ5d05EaWlxTEZWN2VSKzJTdisKVExNb0ZXRGxjeFVmQjRHc1c0cWMzSlpXQy8wTDkxcEdxNm5nSk1RV3NaNnQwOWhkL3hwZTVxbmEzMDFqUXN3bwppWGFsb3BVUVNpWjJld1hLUmp5QllUbWkwMy9WN0dZUG8vWW1IcjJ1MDB6blRKSFpCMEM2NWU3MWltcERPVERGCkpSV3ROQXB3MWIydUhIUSs1UklHeGllNWliR2xHV3VzanNKR0dMbGY4Y25jRlNpc3JhMWgwVkRCaS9rMk5Idm4KSFo1SDVPT0FiNTdXV041U1A5VHU2Y1dWUHVrSXZObGVtTjV2UktIYmpZSDdXSnd0cGdjd0JCZ0dzYjZiZ0hPNQpIa2ZEazljNWQvNGMrQTdOU0VXT043U0NJMUx2YjZrbFJRbk1kSjVWRm9IVW5HU2ZpMzQycU0xSlhDRFpidkVGCjYwMmRNWWFnaUlOdWM3WWc2RjhYcno1UVA1VzRKdDRudW5hV3NnaVdSZUtzWHJpQ1dYdm9Vc2V1VVJIeUw5SWsKZzZST3RuTll4MmF1MGF4Z2VpRk5qdmhnOCt3K0psaXpUeERBOGc4OFp6Slp2S3M1Mm51T2t6bGxRbUVOazlZVwpxaGcva094VTl1amdPTnV2Zmo2K2lSY2x5T0hKTEV6RDhFNlMzcnExTXdBV1FyNStmZkE4MnlxRThZSWtIS3hhCjVJcnVlS1FUYXg2S3Z6SDFNUTRpNkV3eFVUZ0JLMEVOdk1LRDhpQm1TUWFCTitQL05FMENBd0VBQWFOL01IMHcKSFFZRFZSME9CQllFRk1NOEdPTHN3ak1STkp0Zy9QZDBqZG9qQlJyd01COEdBMVVkSXdRWU1CYUFGT2VHMkVWcQpVYkpxdG1VYU90cU9jY29tUWI0SU1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0N3WURWUjBQQkFRREFnR0dNQjBHCkExVWRKUVFXTUJRR0NDc0dBUVVGQndNQkJnZ3JCZ0VGQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUEKZHdHeHVUNHdtQjd0YTlpQm5Fakx5Mm9Bd0pkZXFaYldhRC83aVdpNHl5V04vYldBQ2FVT2gwbDJhdmJTOG1IegpScUh4V0FOSnZhNTN3ZXhsakZlbURpbGNYMlF1YUtoUVNReVNmM3FwbDZzY2cvQUtZc3JHTlpqNVVSMFR3cUpCCmQ4TU9XbVhNM3RMRHdTaWFVY2RyK0I2bkFjNUhzRXpUQmw4WDRxOWhkMktSTytsREZ6alh4Vk8vYXFBZklPMWsKOFhyQksvQ0kyMnlTQWd4cUFZK01lS0R3R1pVTDArdzh6d0FiRGtIZFZsWkpLK0pkaS90dHcyT0syNDNuUlAyMgo2a3ZFN0ZwOHlHRHFESmN6VHdSOE1iY201d2c4VDZBNDEvektGWXE2eHlLQWkwd3U2OGF6SGpDSXBzVTloUG0xCkRLSkRJV1hwTktlaE5ZaCs3eWExVWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlFd1RDQ0E2bWdBd0lCQWdJQkFUQU5CZ2txaGtpRzl3MEJBUVVGQURCZU1Rc3dDUVlEVlFRR0V3Sk9UREUwCk1ESUdBMVVFQ2d3clUyMWhjblFnUTI5dWJtVmpkR1ZrSUZOMWNIQnNhV1Z5SUU1bGRIZHZjbXNnUm05MWJtUmgKZEdsdmJqRVpNQmNHQTFVRUF3d1FTVVJUSUZORFUwNGdVazlQVkNCRFFUQWVGdzB5TURFd01ERXhORFF4TlRkYQpGdzB5T0RBNU1qa3hORFF4TlRkYU1Ha3hDekFKQmdOVkJBWVRBazVNTVRRd01nWURWUVFLREN0VGJXRnlkQ0JECmIyNXVaV04wWldRZ1UzVndjR3hwWlhJZ1RtVjBkMjl5YXlCR2IzVnVaR0YwYVc5dU1TUXdJZ1lEVlFRRERCdEoKUkZNZ1UwTlRUaUJRUVZKVVNVTkpVRUZPVkNCVFZVSWdRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQwpEd0F3Z2dJS0FvSUNBUUMwZ1NvRjltRWZGOE5abVlUZHpQbmQ3aXBSeGtLSU4rcFExWHhPbHlYTGhoNXJRYkl5CkNzSkx4NHNRMnE0VEoxTVNnTWZBb0RualowdTZ2d3lodnh4TUpub0cvellGZzBLZGRaeGdjYVAxZWVxTHlBcWsKT1VhZjJtZ3AzbEg5S0lpdjhKVk5pTHBTVHRtOERTS01KK25xbWdsWloxZTEwd29jOFJ1SFM4T2xmZDVUU0lIVwptTDhYcUlrczJXenNxTWswUmJ0ZmNVV2kybFFzR2dSNGMwNVNUdnFYTkVPditiZ3lNNUF3QkhOV2JIMzZqd2ZCClFEaTZtd3F1aU5hdXd6ZnBnWVh4elVXb041aHhxQzJCREc4TlBTSjdRVU9tK3daMUhuUGxhM2FTYldnQ0ozd00KTElmcTA0YWhZSHdwaXE5alhoNjJQZGNCaW5pcVlzUkFOczkva3UrNEdIbWh2YkZ4aXI3d3NFdzd3bXlCN09tQwpTdEt0NGdKWjRJaVVUaDlzbGJTRjFSVVh6QS9UNjJScEl4dkx1c1JJT2ROQVNWcEM0YVdycmFvZGRkV1ZaZVRjCkJrUzFJdmpxaWxuN01PemZsWEk1REpkbDF6dHY3bHA4Qm55RTJoQ3ZtUStYakY0TUhwSmx4UlFrVWhlL2pwcGkKaUVubWl1MjBsNE04WWVhY3d0MHhvVUNkeUE5dHlzK3hhOUlrZTVxcVVJYXFWdG5lSXdlSUNXclV1MGJESFJKcgpZRStjNEFBcjZUcXhPQlJ0aTdrMDRuSlhqblFNemxqYW9RSHZDWmZtcHJOQ01HNXRIeFJsRGRtdWV1eXJOU0pDCk5QaEZubmtkWGhHL2VOV3Zwd3ZEWUQ0bnBDVUQ4TjY5V0Y2WTFpZVpyRU9XdXJ1aFpabFhaRUwvdHdJREFRQUIKbzM4d2ZUQWRCZ05WSFE0RUZnUVVHTEQ5T0JXdWNGS0tQKzErS2NUZXJCekJkdVF3SHdZRFZSMGpCQmd3Rm9BVQo1NGJZUldwUnNtcTJaUm82Mm81eHlpWkJ2Z2d3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFMQmdOVkhROEVCQU1DCkFZWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUEwR0NTcUdTSWIzRFFFQkJRVUEKQTRJQkFRQVgreEZFcEpwdGovVkRYdEk5d0VWZnFaZjlTbmlIZlhNaTNvb29QbWpibnpOcGQyeEtCU2lxQnY5TgpMTmlkazVVMS9UV3FROWl2U2RPaGJNZzNXMFVTbnVaWG5iTThLQmY4Vjcyc2htcHBMVEpjRUI3cllXZW9QYTc2CmNPRGpYMGI1czlpSGpHM1hiN1R2TE52RjhqWjdWeEEzOHJnNys5T0FEK0lVc0NnQ05NbjFIdmdPZHR0UDRWRWkKMVQ3YzhvbjJSM2taYUs2Zm1laW90STE0eTduOXgrMVhYakhnZDZ6R0hKMUJrRnRsczVyUWlRWUxyM2F6U2RrTwpnd2FyTzhybnY5TkFIeVNSMTFqcDZJVUZ4cGZFYTJQYkQyaFQ3WlB6ZnMxcmRjTVlMVUFscEE0UVdLNjByRXJHCm5GdnpvaUJYWlFjdC9na1ltOXBSUDl4aUNTZGUKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==

  4. Test the connection.

Last updated