diff --git a/.github/build/install.sh b/.github/build/install.sh new file mode 100755 index 0000000..1515b6c --- /dev/null +++ b/.github/build/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +wget "https://get.helm.sh/helm-v3.15.2-linux-amd64.tar.gz" +tar zxf helm-v3.15.2-linux-amd64.tar.gz +mkdir bin +mv linux-amd64/helm ./bin/helm + +go install github.com/yannh/kubeconform/cmd/kubeconform@latest diff --git a/.github/scripts/eval.sh b/.github/scripts/eval.sh new file mode 100755 index 0000000..1547d8d --- /dev/null +++ b/.github/scripts/eval.sh @@ -0,0 +1,20 @@ +#! /bin/bash + +CHARTS=$(pwd)/charts/* +RETURN_VAL=0 +for chart in $CHARTS +do + ./bin/helm dependency build ${chart} + ./bin/helm template ${chart} | kubeconform -strict + + ret=$? + if [ $ret -ne 0 ]; then + RETURN_VAL=$ret + fi +done + +if [ $RETURN_VAL -eq 0 ]; then + echo "Chart evaluation successful !!!" +fi + +exit $RETURN_VAL diff --git a/.github/scripts/lint.sh b/.github/scripts/lint.sh new file mode 100755 index 0000000..93e46c7 --- /dev/null +++ b/.github/scripts/lint.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +CHARTS=./charts/* +for chart in $CHARTS +do + docker run --rm -v $(pwd):/apps alpine/helm:2.9.0 lint $chart +done diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..684db32 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,173 @@ +name: Check PR + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + branches: + - main + +jobs: + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Lint + run: ./.github/scripts/lint.sh + + eval: + runs-on: ubuntu-latest + needs: + - lint + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v5 + with: + go-version: '>=1.17.0' + + - name: Eval + run: | + .github/build/install.sh + .github/scripts/eval.sh + + check-labels: + runs-on: ubuntu-latest + needs: + - lint + - eval + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: '11' + java-package: jdk + + - id: match-label + name: Match semver labels + uses: zwaldowski/match-label-action@v1 + with: + allowed: major, minor, patch + + - uses: zwaldowski/semver-release-action@v2 + name: Semver release + with: + dry_run: true + bump: ${{ steps.match-label.outputs.match }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + comment: + runs-on: ubuntu-latest + needs: + - "check-labels" + + if: always() + steps: + - uses: technote-space/workflow-conclusion-action@v2 + - name: Checkout + uses: actions/checkout@v1 + + - name: Comment PR + if: env.WORKFLOW_CONCLUSION == 'failure' + uses: thollander/actions-comment-pull-request@1.0.2 + with: + message: "Please apply one of the following labels to the PR: 'patch', 'minor', 'major'." + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + prepare-release: + needs: ["check-labels", "comment"] + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - id: bump + uses: zwaldowski/match-label-action@v4 + with: + allowed: major,minor,patch + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v14.6 + + + # prepare yaml parser + - uses: actions/setup-go@v4 + - name: Install yq + run: | + go install github.com/mikefarah/yq/v4@latest + yq --version + + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + + - name: Update versions + shell: bash + run: | + declare -A changedCharts + + for file in ${{ steps.changed-files.outputs.all_changed_and_modified_files }}; do + + echo "$file was changed" + baseFolder=$(cut -d'/' -f1 <<< "$file") + if [ $baseFolder = "charts" ] && [ $file != "charts/README.md" ]; then + chartName=$(cut -d'/' -f2 <<< "$file") + changedCharts[$chartName]=$chartName + fi + done + + for c in "${changedCharts[@]}"; do + # get version from chart yaml + version=$(yq e '.version' "charts/$c/Chart.yaml") + major=$(cut -d'.' -f1 <<< "$version") + minor=$(cut -d'.' -f2 <<< "$version") + patch=$(cut -d'.' -f3 <<< "$version") + + prType=${{ steps.bump.outputs.match }} + echo Update version $version with type $prType + if [ $prType = "major" ]; then + echo Update major + major=$((major+1)) + minor=0 + patch=0 + elif [ $prType = "minor" ]; then + echo Update minor + minor=$((minor+1)) + patch=0 + elif [ $prType = "patch" ]; then + echo Update patch + patch=$((patch+1)) + fi + echo Update version to $major.$minor.$patch for $c + yq e -i '.version = "'$major.$minor.$patch'"' charts/$c/Chart.yaml + done + + - name: Commit files + continue-on-error: true + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git status + echo commit + git commit -m "Update helm chart versions" -a + echo status update + git status + + - name: Push changes + continue-on-error: true + uses: ad-m/github-push-action@master + with: + branch: ${{ github.head_ref }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 9bd410f..719d920 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -52,6 +52,12 @@ jobs: - uses: actions/checkout@v2 + - name: Generate Table of contents + uses: technote-space/toc-generator@v4 + with: + TARGET_PATHS: "./README.md,./doc/README.md" + COMMIT_MESSAGE: "Update ToC" + - uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fd4d4d..46e3e28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,44 +7,104 @@ on: jobs: - generate-version: - name: Generate version - runs-on: ubuntu-latest + generate-version: + name: "Generate version" + runs-on: ubuntu-latest - outputs: - version: ${{ steps.out.outputs.version }} + outputs: + version: ${{ steps.out.outputs.version }} - steps: - - uses: actions/checkout@v2 + steps: + - uses: actions/checkout@v2 - - id: pr - uses: actions-ecosystem/action-get-merged-pull-request@v1.0.1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-java@v1 + with: + java-version: '17' + java-package: jdk - - uses: zwaldowski/semver-release-action@v2 - with: - dry_run: true - bump: ${{ steps.pr.outputs.labels }} - github_token: ${{ secrets.GITHUB_TOKEN }} + - id: pr + uses: actions-ecosystem/action-get-merged-pull-request@v1.0.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Set version output - id: out - run: echo "::set-output name=version::$(echo ${VERSION})" + - name: Match semver label via bash + id: match-label-bash + run: | + LABELS=$(cat <<-END + ${{ steps.pr.outputs.labels }} + END + ) + IFS='\n' read -ra LABEL <<< "$LABELS" + for i in "${LABEL[@]}"; do + echo $i + case $i in + # Will just use the first occurence + 'major'|'minor'|'patch') + echo "RELEASE_LABEL=$i" >> $GITHUB_OUTPUT + break + esac + done + + - uses: zwaldowski/semver-release-action@v2 + with: + dry_run: true + bump: ${{ steps.match-label-bash.outputs.RELEASE_LABEL }} + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Set version output + id: out + run: echo "::set-output name=version::$(echo ${VERSION})" - git-release: - name: Git Release - needs: ["generate-version"] - runs-on: ubuntu-latest + deploy: + name: "Release charts" + needs: + - "generate-version" + runs-on: ubuntu-latest - steps: + steps: + - uses: actions/checkout@v2 - - uses: actions/checkout@v2 + - name: Fetch history + run: git fetch --prune --unshallow + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + run: | + curl -fsSLo get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 + chmod 700 get_helm.sh + ./get_helm.sh + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.5.0 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CR_SKIP_EXISTING: true + + git-release: + name: "Create Git Release" + needs: ["generate-version", "deploy"] + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v2 + + - name: Generate Table of contents + uses: technote-space/toc-generator@v4 + with: + TARGET_PATHS: "./README.md,./doc/README.md" + COMMIT_MESSAGE: "Update ToC" + + - uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: ${{ needs.generate-version.outputs.version }} + prerelease: false + title: ${{ needs.generate-version.outputs.version }} + files: | + LICENSE - - uses: "marvinpinto/action-automatic-releases@latest" - with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: ${{ needs.generate-version.outputs.version }} - title: ${{ needs.generate-version.outputs.version }} - prerelease: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..aae09b3 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,20 @@ +name: Test +on: + push + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: '17' + java-package: jdk + + - name: Execute tests + id: test + run: | + mvn clean integration-test -Ptest \ No newline at end of file diff --git a/README.md b/README.md index c494924..dcc6a46 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # FIWARE Data Space Connector -This repository provides a description of the FIWARE Data Space Connector which has been developed implementing [DSBA Technical Convergence recommendations](https://data-spaces-business-alliance.eu/wp-content/uploads/dlm_uploads/Data-Spaces-Business-Alliance-Technical-Convergence-V2.pdf) as soon as they become enough mature. +The FIWARE Data Space Connector is an integrated suite of components +implementing [DSBA Technical Convergence recommendations](https://data-spaces-business-alliance.eu/wp-content/uploads/dlm_uploads/Data-Spaces-Business-Alliance-Technical-Convergence-V2.pdf), every organization participating +in a data space should deploy to “connect” to a data space. The implementation of these recommendations +is developed as soon as they become enough mature. -If you want to head over directly to the implementation, go to the -FIWARE-Ops [data-space-connector repository](https://github.com/FIWARE-Ops/data-space-connector). +This repository provides a description of the FIWARE Data Space Connector, its technical implementation and deployment +recipes. @@ -14,6 +17,7 @@ FIWARE-Ops [data-space-connector repository](https://github.com/FIWARE-Ops/data- + - [Overview](#overview) - [Components](#components) @@ -24,9 +28,13 @@ FIWARE-Ops [data-space-connector repository](https://github.com/FIWARE-Ops/data- - [Service interaction](#service-interaction) - [Service interaction (H2M)](#service-interaction-h2m) - [Service interaction (M2M)](#service-interaction-m2m) -- [Implementation](#implementation) - - [Examples](#examples) - - [Contract Management via TMForum APIs](#contract-management-via-tmforum-apis) +- [Deployment](#deployment) + - [Local Deployment](#local-deployment) + - [Deployment with Helm](#deployment-with-helm) +- [Testing](#testing) +- [Additional documentation and resources](#additional-documentation-and-resources) + - [Additional documentation](#additional-documentation) + - [Additional Resources](#additional-resources) @@ -73,7 +81,7 @@ Thus, being provided as Helm chart, the FIWARE Data Space Connector can be deplo The following diagram shows a logical overview of the different components of the FIWARE Data Space Connector. -![connector-components](doc/img/Connector_Components.png) +![connector-components](doc/img/flows/Connector_Components.png) Precisely, the connector bundles the following components: @@ -114,7 +122,7 @@ self description. The following displays the different steps during the onboarding. -![flows-onboarding](doc/img/Flows_Onboarding.png) +![flows-onboarding](doc/img/flows/Flows_Onboarding.png) **Steps** @@ -156,7 +164,7 @@ representing a buyer of products in the provider's connector. The following displays the different steps for the consumer registration. -![flows-consumer-registration](doc/img/Flows_Consumer-Registration.png) +![flows-consumer-registration](doc/img/flows/Flows_Consumer-Registration.png) **Steps** @@ -185,7 +193,7 @@ procure access to a specific service linked to a product of the provider. The following displays the different steps for the contract negotiation. -![flows-contract-management](doc/img/Flows_Contract-Management.png) +![flows-contract-management](doc/img/flows/Flows_Contract-Management.png) **Steps** @@ -224,7 +232,7 @@ The following displays the different steps for the two different types of intera #### Service interaction (H2M) -![flows-interaction-h2m](doc/img/Flows_Interaction-H2M.png) +![flows-interaction-h2m](doc/img/flows/Flows_Interaction-H2M.png) **Steps** @@ -251,7 +259,7 @@ The following displays the different steps for the two different types of intera #### Service interaction (M2M) -![flows-interaction-h2m](doc/img/Flows_Interaction-M2M.png) +![flows-interaction-h2m](doc/img/flows/Flows_Interaction-M2M.png) **Steps** @@ -278,31 +286,89 @@ in the [Service Interaction (M2M)](./doc/flows/service-interaction-m2m) document -## Implementation +## Deployment -The implementation of the FIWARE Data Space Connector can be found at -the FIWARE-Ops [data-space-connector repository](https://github.com/FIWARE-Ops/data-space-connector), -which also provides instructions for the deployment. +### Local Deployment +The FIWARE Data Space Connector provides a local deployment of a Minimal Viable Dataspace. +* Find a detailed documentation here: [Local Deployment](./doc/LOCAL.MD) -### Examples +This deployment allows to easily spin up such minimal data space on a local machine, by just using +[Maven](https://maven.apache.org/) and [Docker](https://www.docker.com/) (with [k3s](https://k3s.io/)), and +can be used to try-out the connector, to get familiar with the different components and flows within the data space +or to perform +tests with the different APIs provided. -* Various examples for the deployment of the FIWARE Data Space Connector, including an -entire "Minimal Viable Dataspace", can be found in the - [examples](https://github.com/FIWARE-Ops/data-space-connector/tree/main/examples) folder of the - FIWARE-Ops [data-space-connector repository](https://github.com/FIWARE-Ops/data-space-connector) -* A description and [ArgoCD](https://argo-cd.readthedocs.io/en/stable/)-based deployment of a full demo-setup of a data space following the DSBA recommendations, - including a data service provider based on the FIWARE Data Space Connector, can be found at the - FIWARE-Ops [fiware-gitops repository](https://github.com/FIWARE-Ops/fiware-gitops/tree/master/aws/dsba). This example follows the - [app-of-apps pattern](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping) and is using a - [GitOps pattern]((https://www.gitops.tech/)) approach. - **Note,** that this is currently being reworked and the repository does not contain the latest configuration. - - -### Contract Management via TMForum APIs -A detailed description of the contract management via TMForum APIs can be found in -the [Contract Management](./doc/flows/contract-management) documentation. +### Deployment with Helm + +The Data-Space-Connector is a [Helm Umbrella-Chart](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies), containing all the sub-charts of the different components and their dependencies. Its sources can be found +[here](./charts/data-space-connector). + +The chart is available at the repository ```https://fiware.github.io/data-space-connector/```. You can install it via: + +```shell + # add the repo + helm repo add dsc https://fiware.github.io/data-space-connector/ + # install the chart + helm install dsc/data-space-connector -n -f values.yaml +``` +**Note,** that due to the app-of-apps structure of the connector and the different dependencies between the components, a deployment without providing any configuration values will not work. Make sure to provide a +`values.yaml` file for the deployment, specifying all necessary parameters. This includes setting parameters of the connected data space (e.g., trust anchor endpoints), DNS information (providing Ingress or OpenShift Route parameters), +structure and type of the required VCs, internal hostnames of the different connector components and providing the configuration of the DID and keys/certs. +Also have a look at the [examples](#examples). + +Configurations for all sub-charts (and sub-dependencies) can be managed through the top-level [values.yaml](./charts/data-space-connector/values.yaml) of the chart. It contains the default values of each component and additional parameter shared between the components. The configuration of the applications can be changed under the key ``````, please see the individual applications and there sub-charts for the available options. +Example: +In order to change the image-tag of [Keycloak](./argocd/applications/keycloak/), the values.yaml looks as following: +```yaml +keycloak: + # configuration for the keycloak-sub-chart. Its used as a dependency to the application, thus all config is accessible under the dependency name + keycloak: + image: + tag: LATEST_GREATEST +``` + +The chart is [published and released](./github/workflows/release-helm.yaml) on each merge to master. + + +## Testing + +In order to test the [helm-charts](./charts/) provided for the FIWARE Data Space Connector, an integration-test +framework based on [Cucumber](https://cucumber.io/) and [Junit5](https://junit.org/junit5/) is provided: [it](./it). + +The tests can be executed via: +```shell + mvn clean integration-test -Ptest +``` +They will spin up the [Local Data Space](./doc/deployment-integration/local-deployment/LOCAL.MD) and run +the [test-scenarios](./it/src/test/resources/it/mvds_basic.feature) against it. + + + + + + +## Additional documentation and resources + + +### Additional documentation + +Additional and more detailed documentation about the FIWARE Data Space Connector, +specific flows and its deployment and integration with other frameworks, can be found here: +* [Additional documentation](./doc) + + + + + +### Additional Resources + +Following is a list with additional resources about the FIWARE Data Space Connector and Data Spaces in general: +* [FIWARE Webinar about Data Spaces, its roles and components (by Stefan Wiedemann)](https://www.youtube.com/watch?v=hm5qMlhpK0g) + + + diff --git a/charts/README.md b/charts/README.md new file mode 100644 index 0000000..59584ec --- /dev/null +++ b/charts/README.md @@ -0,0 +1,20 @@ +# Charts + +This directory provides the actual charts of the connector. + + +## Data Space Connector + +The folder [data-space-connector](./data-space-connector) contains the actual FIWARE +Data Space Connector as a [Helm Umbrella Chart](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies). +This includes the `Chart.yaml` with the different depending charts for the components, a `values.yaml` providing default values for the +configuration parameters of the different components, and additional Helm templates. + + +## Trust Anchor + +The folder [trust-anchor](./trust-anchor) contains a minimal example of a Trust Anchor, provided as +a [Helm Umbrella Chart](https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies). +Basically it consists of a Trusted Issuers Registry with an attached database. This is also used +in the local deployment of a Minimal Viable Dataspace described [here](../doc/local-deployment/LOCAL.MD). + diff --git a/charts/data-space-connector/.helmignore b/charts/data-space-connector/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/data-space-connector/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/data-space-connector/Chart.yaml b/charts/data-space-connector/Chart.yaml new file mode 100644 index 0000000..a9785f7 --- /dev/null +++ b/charts/data-space-connector/Chart.yaml @@ -0,0 +1,52 @@ +apiVersion: v2 +name: data-space-connector +description: Umbrella Chart for the FIWARE Data Space Connector, combining all essential parts to be used by a participant. +type: application +version: 7.0.0 +dependencies: + - name: postgresql + condition: postgresql.enabled + repository: oci://registry-1.docker.io/bitnamicharts + version: 13.1.5 + # authentication + - name: vcverifier + condition: vcverifier.enabled + version: 2.9.0 + repository: https://fiware.github.io/helm-charts + - name: credentials-config-service + condition: credentials-config-service.enabled + version: 0.1.5 + repository: https://fiware.github.io/helm-charts + - name: trusted-issuers-list + condition: trusted-issuers-list.enabled + version: 0.6.2 + repository: https://fiware.github.io/helm-charts + - name: mysql + condition: mysql.enabled + version: 9.4.4 + repository: https://charts.bitnami.com/bitnami + # authorization + - name: odrl-pap + condition: odrl-pap.enabled + version: 0.0.22 + repository: https://fiware.github.io/helm-charts + - name: apisix + condition: apisix.enabled + version: 3.1.0 + repository: oci://registry-1.docker.io/bitnamicharts + # data-service + - name: scorpio-broker-aaio + alias: scorpio + condition: scorpio.enabled + repository: https://fiware.github.io/helm-charts + version: 0.4.7 + - name: postgresql + alias: postgis + condition: postgis.enabled + repository: oci://registry-1.docker.io/bitnamicharts + version: 13.1.5 + # issuance + - name: keycloak + condition: keycloak.enabled + version: 21.1.1 + repository: https://charts.bitnami.com/bitnami diff --git a/charts/data-space-connector/templates/_helpers.tpl b/charts/data-space-connector/templates/_helpers.tpl new file mode 100644 index 0000000..62dd817 --- /dev/null +++ b/charts/data-space-connector/templates/_helpers.tpl @@ -0,0 +1,55 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "dsc.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "dsc.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dsc.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "dsc.serviceAccountName" -}} +{{- if .Values.did.serviceAccount.create -}} + {{ default (include "dsc.fullname" .) .Values.did.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.did.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "dsc.labels" -}} +app.kubernetes.io/name: {{ include "dsc.name" . }} +helm.sh/chart: {{ include "dsc.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} diff --git a/charts/data-space-connector/templates/apisix-cm.yaml b/charts/data-space-connector/templates/apisix-cm.yaml new file mode 100644 index 0000000..e765966 --- /dev/null +++ b/charts/data-space-connector/templates/apisix-cm.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: apisix-routes + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + apisix.yaml: |- + routes: + {{- if .Values.apisix.catchAllRoute.enabled }} + - uri: /* + upstream: + nodes: + {{ .Values.apisix.catchAllRoute.upstream.url}}: 1 + type: roundrobin + plugins: + openid-connect: + client_id: {{ .Values.apisix.catchAllRoute.oidc.clientId }} + client_secret: the-secret + bearer_only: true + use_jwks: true + discovery: {{ .Values.apisix.catchAllRoute.oidc.discoveryEndpoint }} + opa: + host: "http://localhost:{{ .Values.opa.port }}" + policy: policy/main + {{- end }} + {{- if .Values.apisix.routes }} + {{ .Values.apisix.routes | nindent 6 }} + {{- end }} + #END \ No newline at end of file diff --git a/charts/data-space-connector/templates/authentication-secrets.yaml b/charts/data-space-connector/templates/authentication-secrets.yaml new file mode 100644 index 0000000..dcc7150 --- /dev/null +++ b/charts/data-space-connector/templates/authentication-secrets.yaml @@ -0,0 +1,14 @@ +{{- if .Values.authentication.generatePasswords.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.authentication.generatePasswords.secretName }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + mysql-root-password: {{ randAlphaNum 30 | b64enc | quote }} + mysql-replication-password: {{ randAlphaNum 30 | b64enc | quote }} + mysql-password: {{ randAlphaNum 30 | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/data-plane-secrets.yaml b/charts/data-space-connector/templates/data-plane-secrets.yaml new file mode 100644 index 0000000..3ff3f46 --- /dev/null +++ b/charts/data-space-connector/templates/data-plane-secrets.yaml @@ -0,0 +1,13 @@ +{{- if .Values.dataplane.generatePasswords.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.dataplane.generatePasswords.secretName }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + postgres-user-password: {{ randAlphaNum 30 | b64enc | quote }} + postgres-admin-password: {{ randAlphaNum 30 | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/database-secrets.yaml b/charts/data-space-connector/templates/database-secrets.yaml new file mode 100644 index 0000000..d1e29f7 --- /dev/null +++ b/charts/data-space-connector/templates/database-secrets.yaml @@ -0,0 +1,13 @@ +{{- if .Values.postgresql.generatePasswords.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.postgresql.generatePasswords.secretName }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + postgres-user-password: {{ randAlphaNum 30 | b64enc | quote }} + postgres-admin-password: {{ randAlphaNum 30 | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/dataplane-registration.yaml b/charts/data-space-connector/templates/dataplane-registration.yaml new file mode 100644 index 0000000..26b9e42 --- /dev/null +++ b/charts/data-space-connector/templates/dataplane-registration.yaml @@ -0,0 +1,39 @@ +{{- if and (eq .Values.scorpio.enabled true) (.Values.scorpio.ccs) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.scorpio.ccs.configMap }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{- include "dsc.labels" . | nindent 4 }} +data: + init.sh: |- + # credentials config service registration + curl -X 'POST' \ + '{{ .Values.scorpio.ccs.endpoint }}/service' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": {{ .Values.scorpio.ccs.id | quote }}, + "defaultOidcScope": {{ .Values.scorpio.ccs.defaultOidcScope.name | quote }}, + {{- if and (.Values.scorpio.ccs.defaultOidcScope.credentialType) (.Values.scorpio.ccs.defaultOidcScope.trustedParticipantsLists) (.Values.scorpio.ccs.defaultOidcScope.trustedIssuersLists) -}} + "oidcScopes": { + {{ .Values.scorpio.ccs.defaultOidcScope.name | quote }}: [ + { + "type": {{ .Values.scorpio.ccs.defaultOidcScope.credentialType | quote }}, + "trustedParticipantsLists": [ + {{ .Values.scorpio.ccs.defaultOidcScope.trustedParticipantsLists | quote }} + ], + "trustedIssuersLists": [ + {{ .Values.scorpio.ccs.defaultOidcScope.trustedIssuersLists | quote }} + ] + } + ] + } + {{- end }} + {{- if .Values.scorpio.ccs.oidcScopes -}} + "oidcScopes": {{- toJson .Values.scorpio.ccs.oidcScopes }} + {{- end }} + }' + +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/did-key-deployment.yaml b/charts/data-space-connector/templates/did-key-deployment.yaml new file mode 100644 index 0000000..484ad41 --- /dev/null +++ b/charts/data-space-connector/templates/did-key-deployment.yaml @@ -0,0 +1,66 @@ +{{- if .Values.did.enabled }} +kind: Deployment +apiVersion: apps/v1 +metadata: + name: did-helper + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: did-helper + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: did-helper + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: default + initContainers: + - name: init-did + image: quay.io/wi_stefan/did-helper:0.1.1 + env: + - name: COUNTRY + value: {{ .Values.did.cert.country }} + - name: STATE + value: {{ .Values.did.cert.state }} + - name: LOCALITY + value: {{ .Values.did.cert.locality }} + - name: ORGANIZATION + value: {{ .Values.did.cert.organization }} + - name: COMMON_NAME + value: {{ .Values.did.cert.commonName }} + - name: STORE_PASS + valueFrom: + secretKeyRef: + name: {{ .Values.did.secret }} + key: store-pass + - name: KEY_ALIAS + value: didPrivateKey + - name: OUTPUT_FORMAT + value: env + - name: OUTPUT_FILE + value: /cert/did.env + volumeMounts: + - name: did-material + mountPath: /cert + + containers: + - name: did-material + imagePullPolicy: Always + image: "lipanski/docker-static-website:2.1.0" + ports: + - name: http + containerPort: 3000 + protocol: TCP + volumeMounts: + - name: did-material + mountPath: /home/static/did-material + volumes: + - name: did-material + emptyDir: { } +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/did-key-ingress.yaml b/charts/data-space-connector/templates/did-key-ingress.yaml new file mode 100644 index 0000000..0f091b4 --- /dev/null +++ b/charts/data-space-connector/templates/did-key-ingress.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.did.enabled .Values.did.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: did-helper + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +spec: + rules: + - host: {{ .Values.did.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: did-helper + port: + name: http +{{- end }} diff --git a/charts/data-space-connector/templates/did-key-service.yaml b/charts/data-space-connector/templates/did-key-service.yaml new file mode 100644 index 0000000..4e10e52 --- /dev/null +++ b/charts/data-space-connector/templates/did-key-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.did.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: did-helper + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +spec: + type: {{ .Values.did.serviceType }} + ports: + - port: {{ .Values.did.port }} + targetPort: 3000 + protocol: TCP + name: http + selector: + app.kubernetes.io/name: did-helper + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/dsconfig-cm.yaml b/charts/data-space-connector/templates/dsconfig-cm.yaml new file mode 100644 index 0000000..955f882 --- /dev/null +++ b/charts/data-space-connector/templates/dsconfig-cm.yaml @@ -0,0 +1,16 @@ +{{- if .Values.dataSpaceConfig.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: dsconfig + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + data-space-configuration.json: |- + { + "supported_models": {{ .Values.dataSpaceConfig.supportedModels | toJson }}, + "supported_protocols": {{ .Values.dataSpaceConfig.supportedProtocols | toJson }}, + "authentication_protocols": {{ .Values.dataSpaceConfig.authenticationProtocols | toJson }} + } +{{- end }} diff --git a/charts/data-space-connector/templates/dsconfig-deployment.yaml b/charts/data-space-connector/templates/dsconfig-deployment.yaml new file mode 100644 index 0000000..77bb12c --- /dev/null +++ b/charts/data-space-connector/templates/dsconfig-deployment.yaml @@ -0,0 +1,38 @@ +{{- if .Values.dataSpaceConfig.enabled }} +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dsconfig + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: dsconfig + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: dsconfig + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: default + containers: + - name: dsconfig-static + imagePullPolicy: Always + image: "lipanski/docker-static-website:2.1.0" + ports: + - name: http + containerPort: 3000 + protocol: TCP + volumeMounts: + - name: dsconfig-json + mountPath: /home/static/.well-known/data-space-configuration + volumes: + - name: dsconfig-json + configMap: + name: dsconfig +{{- end }} diff --git a/charts/data-space-connector/templates/dsconfig-service.yaml b/charts/data-space-connector/templates/dsconfig-service.yaml new file mode 100644 index 0000000..3b5555a --- /dev/null +++ b/charts/data-space-connector/templates/dsconfig-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.dataSpaceConfig.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: dsconfig + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +spec: + type: {{ .Values.dataSpaceConfig.serviceType }} + ports: + - port: {{ .Values.dataSpaceConfig.port }} + targetPort: 3000 + protocol: TCP + name: http + selector: + app.kubernetes.io/name: dsconfig + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/data-space-connector/templates/issuance-secrets.yaml b/charts/data-space-connector/templates/issuance-secrets.yaml new file mode 100644 index 0000000..6bab983 --- /dev/null +++ b/charts/data-space-connector/templates/issuance-secrets.yaml @@ -0,0 +1,15 @@ +{{- if .Values.issuance.generatePasswords.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.issuance.generatePasswords.secretName }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + postgres-user-password: {{ randAlphaNum 30 | b64enc | quote }} + postgres-admin-password: {{ randAlphaNum 30 | b64enc | quote }} + keycloak-admin: {{ randAlphaNum 30 | b64enc | quote }} + store-pass: {{ randAlphaNum 30 | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/opa-cm.yaml b/charts/data-space-connector/templates/opa-cm.yaml new file mode 100644 index 0000000..5a2979b --- /dev/null +++ b/charts/data-space-connector/templates/opa-cm.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: opa-config + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + opa.yaml: |- + services: + - name: bundle-server + url: {{ .Values.opa.resourceUrl }} + bundles: + policies: + service: bundle-server + resource: policies.tar.gz + polling: + min_delay_seconds: {{ .Values.opa.policies.minDelay }} + max_delay_seconds: {{ .Values.opa.policies.maxDelay }} + methods: + service: bundle-server + resource: methods.tar.gz + polling: + min_delay_seconds: {{ .Values.opa.methods.minDelay }} + max_delay_seconds: {{ .Values.opa.methods.maxDelay }} + data: + service: bundle-server + resource: data.tar.gz + polling: + min_delay_seconds: {{ .Values.opa.data.minDelay }} + max_delay_seconds: {{ .Values.opa.data.maxDelay }} + default_decision: /policy/main/allow \ No newline at end of file diff --git a/charts/data-space-connector/templates/participant-registration.yaml b/charts/data-space-connector/templates/participant-registration.yaml new file mode 100644 index 0000000..f0718f2 --- /dev/null +++ b/charts/data-space-connector/templates/participant-registration.yaml @@ -0,0 +1,23 @@ +{{- if .Values.registration.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.registration.configMap }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{- include "dsc.labels" . | nindent 4 }} +data: + init.sh: |- + # credentials config service registration + curl -X 'POST' \ + '{{ .Values.registration.til }}/issuer' \ + -H 'accept: */*' \ + -H 'Content-Type: application/json' \ + -d "{ + \"did\": \"{{ .Values.registration.did }}\", + \"credentials\": { + \"credentialsType\": \"{{ .Values.registration.credentialsType }}\" + } + }" + +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/templates/realm.yaml b/charts/data-space-connector/templates/realm.yaml new file mode 100644 index 0000000..9a623e5 --- /dev/null +++ b/charts/data-space-connector/templates/realm.yaml @@ -0,0 +1,154 @@ +{{- if .Values.keycloak.realm.import }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.keycloak.realm.name }}-realm + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "dsc.labels" . | nindent 4 }} +data: + {{ .Values.keycloak.realm.name }}-realm.json: |- + { + "id": "{{ .Values.keycloak.realm.name }}", + "realm": "{{ .Values.keycloak.realm.name }}", + "displayName": "Keycloak", + "displayNameHtml": "
Keycloak
", + "enabled": true, + "attributes": { + "frontendUrl": "{{ .Values.keycloak.realm.frontendUrl }}", + "issuerDid": "${DID}" + }, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges", + "composite": false, + "clientRole": false, + "containerId": "dome", + "attributes": {} + } + ], + "client": { + {{ .Values.keycloak.realm.clientRoles | nindent 10 }} + } + }, + "groups": [ + ], + "users": [ + {{ .Values.keycloak.realm.users | nindent 8 }} + ], + "clients": [ + {{ .Values.keycloak.realm.clients | nindent 8 }} + ], + "clientScopes": [ + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + ], + "defaultOptionalClientScopes": [ + ], + "components": { + "org.keycloak.protocol.oid4vc.issuance.signing.VerifiableCredentialsSigningService": [ + { + "id": "jwt-signing", + "name": "jwt-signing-service", + "providerId": "jwt_vc", + "subComponents": {}, + "config": { + "keyId": [ + "${DID}" + ], + "algorithmType": [ + "ES256" + ], + "issuerDid": [ + "${DID}" + ], + "tokenType": [ + "JWT" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "a4589e8f-7f82-4345-b2ea-ccc9d4366600", + "name": "test-key", + "providerId": "java-keystore", + "subComponents": {}, + "config": { + "keystore": [ "/did-material/cert.pfx" ], + "keystorePassword": [ "${STORE_PASS}" ], + "keyAlias": [ "didPrivateKey" ], + "keyPassword": [ "${STORE_PASS}" ], + "kid": [ "${DID}"], + "active": [ + "true" + ], + "priority": [ + "0" + ], + "enabled": [ + "true" + ], + "algorithm": [ + "ES256" + ] + } + } + ] + } + } + +{{- end }} \ No newline at end of file diff --git a/charts/data-space-connector/values.yaml b/charts/data-space-connector/values.yaml new file mode 100644 index 0000000..fa90149 --- /dev/null +++ b/charts/data-space-connector/values.yaml @@ -0,0 +1,655 @@ +# -- configuration to be shared between the authentication components +authentication: + generatePasswords: + # -- should a password for the database connection of authentication components be generated in the cluster + enabled: true + #-- name of the secret to put the generated password into + secretName: authentication-database-secret + +# -- configuration to be shared between the dataplane components +dataplane: + generatePasswords: + # -- should a password for the database connection of dataplane components be generated in the cluster + enabled: true + #-- name of the secret to put the generated password into + secretName: data-service-secret + +# -- configuration to be shared between the issuance components +issuance: + generatePasswords: + # -- should a password for the database connection of issuance components be generated in the cluster + enabled: true + #-- name of the secret to put the generated password into + secretName: issuance-secret + +# -- configuration for the mysql to be deployed as part of the connector, see https://github.com/bitnami/charts/tree/main/bitnami/mysql for all options +mysql: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: authentication-mysql + # -- configure authentication to mysql + auth: + # -- name of the secret to take the passowrds from + existingSecret: authentication-database-secret + # -- scripts to be executed on db startup + initdbScripts: + create.sql: | + CREATE DATABASE tildb; + CREATE DATABASE ccsdb; + +# -- configuration for the trusted-issuers-list to be deployed as part of the connector, see https://github.com/FIWARE/helm-charts/tree/main/charts/trusted-issuers-list for all options +trusted-issuers-list: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: trusted-issuers-list + # -- connection to the database + database: + # -- should persistence be used? + persistence: true + # -- name of the db user + username: root + # -- configuration for the existing secret to get the passwords from + existingSecret: + enabled: true + name: authentication-database-secret + key: mysql-root-password + # -- host of the database + host: authentication-mysql + # -- name of the schema inside the db + name: tildb + +# -- configuration for the vcverifier to be deployed as part of the connector, see https://github.com/FIWARE/helm-charts/tree/main/charts/vcverifier for all options +vcverifier: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: verifier + # -- configuration for the m2m flow, in case the tir is requiring authentication + m2m: + # -- we do not need authentication here + authEnabled: false + +# -- configuration for the credentials-config-service to be deployed as part of the connector, see https://github.com/FIWARE/helm-charts/tree/main/charts/credentials-config-service for all options +credentials-config-service: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: credentials-config-service + # -- connection to the database + database: + # -- should persistence be used? + persistence: true + # -- name of the db user + username: root + # -- configuration for the existing secret to get the passwords from + existingSecret: + enabled: true + name: authentication-database-secret + key: mysql-root-password + # -- host of the database + host: authentication-mysql + # -- name of the schema inside the db + name: ccsdb + +# -- configuration for the postgresql to be deployed as part of the connector, see https://github.com/bitnami/charts/tree/main/bitnami/postgresql for all options +postgresql: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: postgresql + generatePasswords: + # -- should a password for the database be generated in the cluster + enabled: true + # -- name of the secret to store the password in + secretName: database-secret + # -- configure authentication to mysql + auth: + # -- name of the secret to take the passowrds from + existingSecret: database-secret + # -- key of the secrets inside the secret + secretKeys: + adminPasswordKey: postgres-admin-password + userPasswordKey: postgres-user-password + # -- configuration for the primary of the db + primary: + # -- scripts to be run on intialization + initdb: + scripts: + create.sh: | + psql postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE pap;" + psql postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE keycloak;" + +# -- configuration for the odrl-pap to be deployed as part of the connector, see https://github.com/FIWARE/helm-charts/tree/main/charts/odrl-pap for all options +odrl-pap: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: odrl-pap + # -- configuration about the image to be used + deployment: + image: + repository: quay.io/fiware/odrl-pap + tag: 0.1.0 + pullPolicy: Always + # -- connection to the database + database: + # -- url to connect the db at + url: jdbc:postgresql://postgresql:5432/pap + # -- username to access the db + username: postgres + # -- secret to take the password from + existingSecret: + enabled: true + name: database-secret + key: postgres-admin-password + +# -- configuration for the open-policy-agent to be deployed as part of the connector, as a sidecar to apisix +opa: + # -- should an opa sidecar be deployed to apisix + enabled: true + # -- address of the pap to get the policies from + resourceUrl: http://odrl-pap:8080/bundles/service/v1 + # -- port to make opa available at + port: 8181 + # -- pull delays for the policies bundle + policies: + minDelay: 2 + maxDelay: 4 + # -- pull delays for the methods bundle + methods: + minDelay: 1 + maxDelay: 3 + # -- pull delays for the data bundle + data: + minDelay: 1 + maxDelay: 15 + +# -- configuration for apisix to be deployed as part of the connector, see https://github.com/bitnami/charts/tree/main/bitnami/apisix for all options +apisix: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- configuration in regard to the apisix control plane + controlPlane: + # -- resources config to be set + resourcesPreset: micro + # -- should it be enabled + enabled: true + # -- configuration in regard to the apisix ingressController + ingressController: + # -- should it be enabled + enabled: false + # -- configuration in regard to the apisix etcd + etcd: + # -- should it be enabled + enabled: true + # -- persistence configuration of etcd + persistence: + # -- should it be enabled + enabled: false + # -- configuration in regard to the apisix dataplane + dataPlane: + # -- resources config to be set + resourcesPreset: micro + # -- configuration for extra configmaps to be deployed + extraConfig: + deployment: + # -- allows to configure apisix through a yaml file + role_data_plane: + config_provider: yaml + # -- extra volumes + # we need `routes` to declaratively configure the routes + # and the config for the opa sidecar + extraVolumes: + - name: routes + configMap: + name: apisix-routes + - name: opa-config + configMap: + name: opa-config + # -- extra volumes to be mounted + extraVolumeMounts: + - name: routes + mountPath: /usr/local/apisix/conf/apisix.yaml + subPath: apisix.yaml + # -- sidecars to be deployed for apisix + sidecars: + # -- we want to deploy the open-policy-agent as a pep + - name: open-policy-agent + image: openpolicyagent/opa:0.64.1 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8181 + protocol: TCP + # opa should be started to listen at 8181 and get its config from the mounted config yaml + args: + - "run" + - "--ignore=.*" # exclude hidden dirs created by Kubernetes + - "--server" + - "-l" + - "debug" + - "-c" + - "/config/opa.yaml" + - "--addr" + - "0.0.0.0:8181" + volumeMounts: + - name: opa-config + mountPath: /config + # -- configuration of a catchAll-route(e.g. /*) + catchAllRoute: + # -- should it be enabled + enabled: true + # -- configuration to connect the upstream broker + upstream: + url: http://my-broker:8000 + # -- configuration to verify the jwt, coming from the verifier + oidc: + clientId: mySecuredService + discoveryEndpoint: http://verifier:3000/services/mySecuredService/.well-known/openid-configuration + + # -- configuration of routes for apisix + routes: +# - uri: /myRoute +# upstream: +# nodes: +# http://my-upstream-service:8080: 1 +# type: roundrobin +# plugins: +# openid-connect: +# client_id: test-id +# client_secret: the-secret +# bearer_only: true +# use_jwks: true +# discovery: http://the-service/.well-known/openid-configuration +# opa: +# host: "http://localhost:8181" +# policy: policy/main/allow + +# -- configuration for the postgresql to be deployed as part of the connector, see https://github.com/bitnami/charts/tree/main/bitnami/postgresql for all options +postgis: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- overrides the generated name, provides stable service names - this should be avoided if multiple instances are available in the same namespace + fullnameOverride: data-service-postgis + # -- overrides the generated name, provides stable service names - this should be avoided if multiple instances are available in the same namespace + nameOverride: data-service-postgis + ## auth configuration for the database + auth: + existingSecret: data-service-secret + secretKeys: + adminPasswordKey: postgres-admin-password + userPasswordKey: postgres-user-password + ## configuration of the postgres primary replicas + primary: + ## provide db initialization + initdb: + ## provide scripts for initialization + scripts: + # -- enable the postgis extension and create the database as expected by scorpio + enable.sh: | + psql postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432 -c "CREATE EXTENSION postgis;" + psql postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE ngb;" + +## configuration of the context-broker - see https://github.com/FIWARE/helm-charts/tree/main/charts/scorpio-broker-aaio for details +scorpio: + fullnameOverride: data-service-scorpio + # -- should scorpio be enabled + enabled: true + ## configuration of the image to be used + image: + # -- repository to be used - resource friendly all-in-one-runner without kafka + repository: scorpiobroker/all-in-one-runner + # -- tag of the image to be used - latest java image without kafka + tag: java-4.1.11 + ## configuration of the database to be used by broker + db: + # -- host of the db + dbhost: data-service-postgis + # -- username to be used + user: postgres + existingSecret: + enabled: true + name: data-service-secret + key: postgres-admin-password + ## configuration of the readiness probe + readinessProbe: + # -- path to be used for the readiness probe, older versions used /actuator/health + path: /q/health + ## configuration of the liveness probe + livenessProbe: + # -- path to be used for the readiness probe, older versions used /actuator/health + path: /q/health + ## configuration to be used for the service offered by scorpio + service: + # -- ClusterIP is the recommended type for most clusters + type: ClusterIP + # -- configuration to register the dataplane at the credentials-config-service + ccs: + # -- endpoint of the ccs to regsiter at + endpoint: http://credentials-config-service:8080 + # -- configmap to get the registration information from + configMap: scorpio-registration + # -- service id of the data-service to be used + id: data-service + # -- default scope to be created for the data plane + defaultOidcScope: + # -- name of the scope + name: default + # -- name of the default credential to be configured + credentialType: VerifiableCredential + # -- needs to be updated for the concrete dataspace + trustedParticipantsLists: http://tir.trust-anchor.org + trustedIssuersLists: http://trusted-issuers-list:8080 + # -- additional init containers to be used for the dataplane + initContainers: + # -- curl container to register at the credentials config service + - name: register-credential-config + image: quay.io/curl/curl:8.1.2 + command: [ "/bin/sh", "-c", "/bin/init.sh" ] + volumeMounts: + - name: scorpio-registration + mountPath: /bin/init.sh + subPath: init.sh + # -- additonal volumes to be mounted for the dataplane + additionalVolumes: + - name: scorpio-registration + configMap: + name: scorpio-registration + defaultMode: 0755 + +## configuration of the keycloak - see https://github.com/bitnami/charts/tree/main/bitnami/keycloak for details +keycloak: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- disable the security context, required by the current quarkus container, will be solved in the future chart versions of keycloak + containerSecurityContext: + enabled: false + # -- keycloak image to be used - set to preview version of 25.0.0, since no other is available yet + image: + registry: quay.io + # until 25 is released, we have to use a snapshot version + repository: wi_stefan/keycloak + tag: 25.0.0-PRE + pullPolicy: Always + command: + - /bin/bash + # -- we need the did of the participant here. when its generated with the did-helper, we have to get it first and replace inside the realm.json through env-vars + args: + - -ec + - | + #!/bin/sh + export $(cat /did-material/did.env) + /opt/keycloak/bin/kc.sh start --features oid4vc-vci --import-realm + service: + ports: + http: 8080 + # -- authentication config for keycloak + auth: + existingSecret: issuance-secret + passwordSecretKey: keycloak-admin + adminUser: keycloak-admin + # -- should the db be deployed as part of the keycloak chart + postgresql: + enabled: false + # -- host of the external db to be used + externalDatabase: + host: postgresql + + # -- the default init container is deactivated, since it conflicts with the non-bitnami image + enableDefaultInitContainers: false + + # -- extra volumes to be mounted + extraVolumeMounts: + - name: empty-dir + mountPath: /opt/keycloak/lib/quarkus + subPath: app-quarkus-dir + - name: qtm-temp + mountPath: /qtm-tmp + - name: did-material + mountPath: /did-material + - name: did-material + mountPath: "/etc/env" + readOnly: true + - name: realms + mountPath: /opt/keycloak/data/import + + extraVolumes: + - name: did-material + emptyDir: { } + - name: qtm-temp + emptyDir: { } + - name: realms + configMap: + name: test-realm-realm + + # -- extra env vars to be set. we require them at the moment, since some of the chart config mechanisms only work with the bitnami-image + extraEnvVars: + # indicates ssl is terminated at the edge + - name: KC_PROXY + value: "edge" + # point the transaction store to the (writeable!) empty volume + - name: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_DIRECTORY + value: /qtm-tmp + # config for the db connection + - name: KC_DB_URL_HOST + value: postgresql + - name: KC_DB_URL_DATABASE + value: keycloak + - name: KC_DB_USERNAME + value: postgres + - name: KC_DB_PASSWORD + valueFrom: + secretKeyRef: + name: database-secret + key: postgres-admin-password + # password for reading the key store connected to the did + - name: STORE_PASS + valueFrom: + secretKeyRef: + name: issuance-secret + key: store-pass + # keycloak admin password + - name: KC_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: issuance-secret + key: keycloak-admin + + # -- init containers to be run with keycloak + initContainers: + # workaround required by the current quarkus distribution, to make keycloak working + - name: read-only-workaround + image: quay.io/wi_stefan/keycloak:25.0.0-PRE + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + cp -r /opt/keycloak/lib/quarkus/* /quarkus + volumeMounts: + - name: empty-dir + mountPath: /quarkus + subPath: app-quarkus-dir + + # retrieve all did material required for the realm and store it to a shared folder + - name: get-did + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + apt-get -y update; apt-get -y install wget + cd /did-material + wget http://did-helper:3000/did-material/cert.pfx + wget http://did-helper:3000/did-material/did.env + volumeMounts: + - name: did-material + mountPath: /did-material + + # register the issuer at the trusted issuers registry - will only work if that one is publicly accessible + - name: register-at-tir + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + source /did-material/did.env + apt-get -y update; apt-get -y install curl + curl -X 'POST' 'http://tir.trust-anchor.svc.cluster.local:8080/issuer' -H 'Content-Type: application/json' -d "{\"did\": \"${DID}\", \"credentials\": []}" + volumeMounts: + - name: did-material + mountPath: /did-material + + # -- configuration of the realm to be imported + realm: + # -- should the realm be imported + import: true + # -- name of the realm + name: test-realm + # -- frontend url to be used for the realm + frontendURL: http://localhost:8080 + # -- client roles to be imported - be aware the env vars can be used and will be replaced + clientRoles: | + "${DID}": [ + { + "name": "ADMIN", + "description": "Is allowed to do everything", + "clientRole": true + } + ] + # -- users to be imported - be aware the env vars can be used and will be replaced + users: | + { + "username": "admin-user", + "enabled": true, + "email": "admin@provider.org", + "firstName": "Test", + "lastName": "Admin", + "credentials": [ + { + "type": "password", + "value": "test" + } + ], + "clientRoles": { + "${DID}": [ + "ADMIN" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + ] + } + # -- clients to be imported - be aware the env vars can be used and will be replaced + clients: | + { + "clientId": "${DID}", + "enabled": true, + "description": "Client to manage itself", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "oid4vc", + "attributes": { + "client.secret.creation.time": "1675260539", + "vc.natural-person.format": "jwt_vc", + "vc.natural-person.scope": "NaturalPersonCredential", + "vc.verifiable-credential.format": "jwt_vc", + "vc.verifiable-credential.scope": "VerifiableCredential" + }, + "protocolMappers": [ + { + "name": "target-role-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-target-role-mapper", + "config": { + "subjectProperty": "roles", + "clientId": "${DID}", + "supportedCredentialTypes": "NaturalPersonCredential" + } + }, + { + "name": "target-vc-role-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-target-role-mapper", + "config": { + "subjectProperty": "roles", + "clientId": "${DID}", + "supportedCredentialTypes": "VerifiableCredential" + } + }, + { + "name": "context-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-context-mapper", + "config": { + "context": "https://www.w3.org/2018/credentials/v1", + "supportedCredentialTypes": "VerifiableCredential,NaturalPersonCredential" + } + }, + { + "name": "email-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-user-attribute-mapper", + "config": { + "subjectProperty": "email", + "userAttribute": "email", + "supportedCredentialTypes": "NaturalPersonCredential" + } + } + ], + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } + +# -- configuration for the did-helper, should only be used for demonstrational deployments, see https://github.com/wistefan/did-helper +did: + enabled: false + +# -- configuration for registering a participant at the til, will most probably only be used in demonstrational enviornments +registration: + enabled: false + +# -- configuration for the .well-known/data-space-configuration endpoint document +dataSpaceConfig: + enabled: false + + # -- Kubernetes Service type + serviceType: ClusterIP + + # -- Kubernetes Service port + port: 3002 + + # -- Supported data models by the service (array of links to JSON schemas) + supportedModels: [] + + # -- Supported protocols (e.g.: http,https) + supportedProtocols: [] + + # -- Supported authentication protocols (e.g.: oid4vp) + authenticationProtocols: [] diff --git a/charts/trust-anchor/.helmignore b/charts/trust-anchor/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/trust-anchor/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/trust-anchor/Chart.yaml b/charts/trust-anchor/Chart.yaml new file mode 100644 index 0000000..6eebc53 --- /dev/null +++ b/charts/trust-anchor/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: trust-anchor +description: Umbrella Chart to provide a minimal trust anchor for a FIWARE Dataspace +version: 2.0.0 +dependencies: + - name: trusted-issuers-list + condition: trusted-issuers-list.enabled + version: 0.6.2 + repository: https://fiware.github.io/helm-charts + - name: mysql + condition: mysql.enabled + version: 9.4.4 + repository: https://charts.bitnami.com/bitnami diff --git a/charts/trust-anchor/templates/_helpers.tpl b/charts/trust-anchor/templates/_helpers.tpl new file mode 100644 index 0000000..0ef5783 --- /dev/null +++ b/charts/trust-anchor/templates/_helpers.tpl @@ -0,0 +1,55 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "trust-anchor.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "trust-anchor.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "trust-anchor.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "trust-anchor.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "trust-anchor.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "trust-anchor.labels" -}} +app.kubernetes.io/name: {{ include "trust-anchor.name" . }} +helm.sh/chart: {{ include "trust-anchor.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} diff --git a/charts/trust-anchor/templates/secrets.yaml b/charts/trust-anchor/templates/secrets.yaml new file mode 100644 index 0000000..824960f --- /dev/null +++ b/charts/trust-anchor/templates/secrets.yaml @@ -0,0 +1,14 @@ +{{- if .Values.generatePasswords.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.generatePasswords.secretName }} + namespace: {{ $.Release.Namespace | quote }} + labels: + {{ include "trust-anchor.labels" . | nindent 4 }} +data: + mysql-root-password: {{ randAlphaNum 30 | b64enc | quote }} + mysql-replication-password: {{ randAlphaNum 30 | b64enc | quote }} + mysql-password: {{ randAlphaNum 30 | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/trust-anchor/values.yaml b/charts/trust-anchor/values.yaml new file mode 100644 index 0000000..96632a5 --- /dev/null +++ b/charts/trust-anchor/values.yaml @@ -0,0 +1,43 @@ +# -- configuration to be shared between the trust-anchor components +generatePasswords: + # -- should a password for the database connection of trust-anchor components be generated in the cluster + enabled: true + #-- name of the secret to put the generated password into + secretName: mysql-database-secret + +# -- configuration for the mysql to be deployed as part of the trust-anchor, see https://github.com/bitnami/charts/tree/main/bitnami/mysql for all options +mysql: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: trust-anchor-mysql + # -- configure authentication to mysql + auth: + # -- name of the secret to take the passowrds from + existingSecret: mysql-database-secret + # -- scripts to be executed on db startup + initdbScripts: + create.sql: | + CREATE DATABASE tirdb; + +# -- configuration for the trusted-issuers-list to be deployed as part of the trust-anchor, see https://github.com/FIWARE/helm-charts/tree/main/charts/trusted-issuers-list for all options +trusted-issuers-list: + # -- should it be enabled? set to false if one outside the chart is used. + enabled: true + # -- allows to set a fixed name for the services + fullnameOverride: tir + # -- connection to the database + database: + # -- should persistence be used? + persistence: true + # -- name of the db user + username: root + # -- configuration for the existing secret to get the passwords from + existingSecret: + enabled: true + name: mysql-database-secret + key: mysql-root-password + # -- host of the database + host: trust-anchor-mysql + # -- name of the schema inside the db + name: tirdb diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..c59234b --- /dev/null +++ b/doc/README.md @@ -0,0 +1,99 @@ +# Additional documentation + +This directory provides additional and more detailed documentation about the FIWARE Data Space Connector, +specific flows and its deployment and integration with other frameworks. + + + + +
+Table of Contents + + + + + +- [Details about flows and interfaces](#details-about-flows-and-interfaces) + - [Contract Management](#contract-management) + - [M2M Service Interaction](#m2m-service-interaction) +- [Deployment / Integration](#deployment--integration) + - [Local deployment of Minimal Viable Dataspace (helm/k3s)](#local-deployment-of-minimal-viable-dataspace-helmk3s) + - [Packet Delivery Company (ArgoCD)](#packet-delivery-company-argocd) + - [Integration with AWS Garnet Framework (formerly AWS Smart Territory Framework)](#integration-with-aws-garnet-framework-formerly-aws-smart-territory-framework) + + + + +
+ + + +## Details about flows and interfaces + +### Contract Management + +The FIWARE Data Space Connector provides components to perform contract management based on the TMForum APIs. + +More information can be found here: +* [Contract Management](./flows/contract-management) - Information about the Contract Management and its + authentication/authorization +* [TMForum contract management example](./flows/contract-management/tmf) - Example requests to interact + with the TMForum APIs + + + +### M2M Service Interaction + +A detailed description about the steps to be performed in a Machine-To-Machine (M2M) service interaction +can be found here: +* [Service Interaction (M2M)](./flows/service-interaction-m2m) + + + + + +## Deployment / Integration + +### Local deployment of Minimal Viable Dataspace (helm/k3s) + +This is an example of a "Minimal Viable Dataspace", consisting of a fictitious data service +provider called M&P Operations Inc. (using the FIWARE Data Space Connector), a data service consumer +called Fancy Marketplace Co. and the +data space's trust anchor. + +The service is provided by the Scorpio Context via the NGSI-LD API, offering access to +energy report entities. + +The example uses [k3s](https://k3s.io/) and helm for deployment on a local machine. + +More information can be found here: +* [Local Deployment](./deployment-integration/local-deployment/LOCAL.MD) + + + +### Packet Delivery Company (ArgoCD) + +This is an example of a data service provider called Packet Delivery Company (PDC). + +The deployment is performed via +[GitOps pattern](https://www.gitops.tech/) and [ArgoCD](https://argo-cd.readthedocs.io/en/stable/). + +The configuration can be found at the +[fiware-gitops repository](https://github.com/FIWARE-Ops/fiware-gitops/tree/master/aws/dsba/packet-delivery/data-space-connector). + +**Note,** that this is currently being reworked and above repository does not contain the latest configuration. + + + + +### Integration with AWS Garnet Framework (formerly AWS Smart Territory Framework) + +This is an example of a data service provider that is integrated with the +[AWS Garnet Framwork (formerly AWS STF)](https://github.com/aws-samples/aws-stf). + +In general, this example deploys a data service provider based on the Data Space Connector, +but integrating the FIWARE Context Broker from the STF. + +More information can be found here: +* [Integration with AWS Garnet Framework](./deployment-integration/aws-garnet/) + diff --git a/doc/deployment-integration/aws-garnet/README.md b/doc/deployment-integration/aws-garnet/README.md new file mode 100644 index 0000000..823006b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/README.md @@ -0,0 +1,150 @@ +# Integration with AWS Garnet Framework + +> :construction: Please note, that this document is currently being reworked for the latest updates of the +> connector and does not contain the latest information + +## Overview + +AWS Garnet Framework is an open-source framework aimed at simplifying the creation and operation of interoperable platforms across diverse domains, including Smart Cities, Energy, Agriculture, and more. +Compliant with the NGSI-LD open standard and harnessing NGSI-LD compliant Smart Data Models, this solution promotes openness and efficiency. +At its core, AWS Garnet Framework integrates the FIWARE Context Broker, an integral component that facilitates data management. +[In the official project GitHub repository](https://github.com/awslabs/garnet-framework), you'll find the necessary resources to deploy both the FIWARE Context Broker and the Garnet IoT module as separate AWS Cloud Development Kit (CDK) nested stacks, offering a flexible and modular approach to enhance and integrate existing solutions over time. + +For the context of Data Spaces, the AWS Garnet Framwork can be extended with the capabilities of the FIWARE Data Spaces Connector, which can instrument an existing deployment of the FIWARE Context Broker, as seen in other examples of this repository. + +In this example, the procedure to deploy the packet delivery service provider named IPS on AWS is provided. This deployment pattern can be reused to implement data spaces use cases requiring the infrastructure of the FIWARE Data Spaces Connector. + +## Prerequisites + +This deployment example focuses on 2 possible initial configurations of infrastructure: + +* 1/ No existing AWS Garnet Framework deployment in the AWS Account + +![Target Architecture for a fresh deployment of AWS Garnet Framework with the DS Connector](./static-assets/garnet-ds-connector-scenario1.png) + +* 2/ Existing AWS Garnet Framework deployment in the AWS Account with a Context Broker on AWS ECS Fargate + +![Target Architecture for extending the deployment of an existing AWS Garnet Framework](./static-assets/garnet-ds-connector-scenario2.png) + +In any of the previous cases, an Amazon EKS Cluster is needed to deploy the Data Space Connector. However, if there is an existing Amazon EKS Cluster in your AWS, it can be leveraged for this deployment and no additional cluster must be created. The next steps will help deploying a new cluster from scratch for the connector deployment. + +### Amazon EKS Cluster Creation +If the creation of a dedicated Kubernetes cluster is considered for the deployment of the FIWARE Data Spaces Connector, it is recommended that users follow the instructions to create a new Amazon EKS Cluster available in the [official Amazon EKS Immersion Workshop](https://catalog.workshops.aws/eks-immersionday/en-US/introduction#confirm-eks-setup) + +#### AWS EKS Cluster Setup with Fargate Profile + +* Assign environment variables to choose the deployment parameters +```shell +export AWS_REGION=eu-west-1 +export ekscluster_name="fiware-dsc-cluster" +``` + +* Create the VPC to host the Amazon EKS cluster on your AWS Account - update the `eks-vpc-3az.yaml` file to select the desired region for your deployment + +```shell +aws cloudformation deploy --stack-name "eks-vpc" --template-file "./yaml/eks-vpc-3az.yaml" --capabilities CAPABILITY_NAMED_IAM +``` + +* Store the VPC ID in an environment variable + +```shell +export vpc_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=eks-vpc | jq -r '.Vpcs[].VpcId') +echo $vpc_ID +``` + +* Export the Subnet ID, CIDR, and Subnet Name to a text file for tracking + +```shell +aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' +echo $vpc_ID > vpc_subnet.txt +aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' >> vpc_subnet.txt +cat vpc_subnet.txt +``` + +* Store VPC ID, Subnet IDs as environment variables that will be used on next steps + +```shell +export PublicSubnet01=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PublicSubnet01/{print $1}') +export PublicSubnet02=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PublicSubnet02/{print $1}') +export PublicSubnet03=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PublicSubnet03/{print $1}') +export PrivateSubnet01=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PrivateSubnet01/{print $1}') +export PrivateSubnet02=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PrivateSubnet02/{print $1}') +export PrivateSubnet03=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_ID | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eks-vpc-PrivateSubnet03/{print $1}') +echo "export vpc_ID=${vpc_ID}" | tee -a ~/.bash_profile +echo "export PublicSubnet01=${PublicSubnet01}" | tee -a ~/.bash_profile +echo "export PublicSubnet02=${PublicSubnet02}" | tee -a ~/.bash_profile +echo "export PublicSubnet03=${PublicSubnet03}" | tee -a ~/.bash_profile +echo "export PrivateSubnet01=${PrivateSubnet01}" | tee -a ~/.bash_profile +echo "export PrivateSubnet02=${PrivateSubnet02}" | tee -a ~/.bash_profile +echo "export PrivateSubnet03=${PrivateSubnet03}" | tee -a ~/.bash_profile +source ~/.bash_profile +``` + +* Use the provided script `eks-cluster-fargateProfiler.sh` [available in this repository](./scripts/eks-cluster-fargateProfiler.sh) to populate your resources IDs to instantiate the Amazon EKS Cluster template + +```shell +chmod +x ./scripts/eks-cluster-fargateProfiler.sh +./scripts/eks-cluster-fargateProfiler.sh +``` + +* Create the Amazon EKS Cluster with Fargate Profile using `eksctl` + +```shell +eksctl create cluster --config-file=./yaml/eks-cluster-3az.yaml +``` + +* Create an IAM Identity Mapping to access your Amazon EKS cluster metadata using the AWS Console + +```shell +eksctl create iamidentitymapping --cluster fiware-dsc-cluster --arn arn:aws:iam:::role/ --group system:masters --username admin +``` + +* Check if your cluster is running properly once the Amazon CloudFormation Stack creation is complete + +```shell +kubectl get svc +``` + +* Configuring OIDC ID Provider(IdP) to EKS cluster allows you to use AWS IAM roles for Kubernetes service accounts, and this requires an IAM OIDC provider in the cluster. Let's run the command below to integrate OIDC into the cluster. + +```shell +eksctl utils associate-iam-oidc-provider --region ${AWS_REGION} --cluster fiware-dsc-cluster --approve +``` + +#### (OPTIONAL) Install [AWS Load Balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) add-on to manage ingress configuration +AWS Load Balancer Controller is a Kubernetes add-on that manages AWS Elastic Load Balancers(ELB) used by Kubernetes cluster. +This controller provides: + +* Provision new AWS ALB when Kubernetes Ingress is created. +* Provision new AWS NLB when Kubernetes LoadBalancer is created. + +It is recommended to follow the official AWS documentation to install the AWS Load Balancer Controller add-on to control ingress. The step-by-step procedure is available in [this link](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) + +#### nginx Ingress Controller Configuration +In AWS, we use a Network load balancer (NLB) to expose the Ingress-Nginx Controller behind a Service of ```Type=LoadBalancer```. It is advised that the [official Installation Guide is followed for the next steps](https://kubernetes.github.io/ingress-nginx/deploy/#aws) +A short version of the procedure is reproduced below for a quick setup: + +* Create an AWS IAM Policy for the Ingress Controller using the provided file `./policies/aws-lbc-iam_policy.json`. The JSON file can also be found [here](https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json) + +```shell +aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://./policies/aws-lbc-iam_policy.json +``` + +* Create an IAM Role and ServiceAccount for the AWS Load Balancer controller + +```shell +eksctl create iamserviceaccount --cluster=fiware-dsc-cluster --namespace=kube-system --name=ingress-nginx-controller --attach-policy-arn=arn:aws:iam::${ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --region ${AWS_REGION} --approve +``` + +* Deploy the Kubernetes Service for the nginx Ingress Controller on your cluster using the provided file `./yaml/nginx-ingress-controller.yaml` . The default deployment file is also available in [this link](https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/aws/nlb-with-tls-termination/deploy.yaml) + +```shell +kubectl apply -n kube-system -f ./yaml/nginx-ingress-controller.yaml +``` + +## Next Steps +Once your Amazon EKS Cluster is ready, head to the specific step-by-step procedure that best describes your current environment in the following links of this documentation: + +* [1/ No existing AWS Garnet Framework deployment in the AWS Account](./scenario-1-deployment/) + +* [2/ Existing AWS Garnet Framework deployment in the AWS Account with a Context Broker on AWS ECS Fargate](./scenario-2-deployment/) diff --git a/doc/deployment-integration/aws-garnet/policies/aws-lbc-iam_policy.json b/doc/deployment-integration/aws-garnet/policies/aws-lbc-iam_policy.json new file mode 100644 index 0000000..7944f2a --- /dev/null +++ b/doc/deployment-integration/aws-garnet/policies/aws-lbc-iam_policy.json @@ -0,0 +1,241 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", + "wafv2:GetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:AssociateWebACL", + "wafv2:DisassociateWebACL", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "arn:aws:ec2:*:*:security-group/*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "CreateSecurityGroup" + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Resource": "arn:aws:ec2:*:*:security-group/*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule" + ], + "Resource": "*" + } + ] +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/README.md b/doc/deployment-integration/aws-garnet/scenario-1-deployment/README.md new file mode 100644 index 0000000..8dcbce4 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/README.md @@ -0,0 +1,94 @@ +# Integration with AWS Garnet Framework + +## 1/ No existing AWS Garnet Framework deployment in the AWS Account +For this scenario, it is recommended that the complete Helm Chart for the Data Spaces Connector is deployed to a Kubernetes Cluster in the service Amazon Elastic Kubernetes Service ([AWS EKS](https://aws.amazon.com/eks/)). +In this case, the FIWARE Context Broker will be hosted by a pod in the Kubernetes cluster and the integration to the AWS Garnet Framework will be performed by deploying only the [AWS Garnet IoT module](https://github.com/aws-samples/aws-stf-core#stf-iot) of the framework. Further configuration will include streamlining the Garnet IoT pipeline to the Internal Service Load Balancer associated to the EKS cluster. + +
+ +![Target Architecture for a fresh deployment of AWS Garnet Framework with the DS Connector](../static-assets/garnet-ds-connector-scenario1.png) + +
+ +### IPS Service Provider Deployment in Amazon EKS +This section covers the setup of the prerequisites of the IPS Service Provider examples of this repository, available in [this reference](../service-provider-ips/README.md). + +* IPS Kubernetes namespace creation + +```shell +kubectl create namespace ips +``` + +* Add FIWARE Data Space Connector Remote repository + +```shell +helm repo add dsc https://fiware-ops.github.io/data-space-connector/ +``` + +* Install the Helm Chart using the provided file `./yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml` [available in this repository](./yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml) + +```shell +helm install -n ips -f ./yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml ips-dsc dsc/data-space-connector +``` + +### Deployment of AWS Garnet Framework IoT Module +An AWS CDK project modified from the AWS Garnet Framework main project is available in [this repository](./aws-garnet-iot-module/). The project was modified so ONLY the AWS Garnet Framwrork IoT Module is deployed once the CDK stacks are deployed. To integrate this module to the Context Broker deployed in the Amazon EKS Cluster, 2 main parameters must be set in the `./aws-garnet-iot-module/parameters.ts` file : + +```shell + // FIWARE DATA SPACE CONNECTOR PARAMETERS + amazon_eks_cluster_load_balancer_dns: "", + amazon_eks_cluster_load_balancer_listener_arn: "", +``` + +Edit the file including the respective strings referencing your Load Balancer resource for the Data Space Connector deployed in the Amazon EKS Cluster in your AWS Account. + +Then, deploy the CDK stack using the following commands: + +```shell +cd aws-garnet-iot-module +``` + +```shell +npm install +``` + +```shell +cdk bootstrap +``` + +```shell +cdk deploy +``` + +## Other Resources - Troubleshooting +Once the Data Space Connector is deployed via the Helm chart in your cluster, additional scripts are also provided in this [repository](../scripts/) to help any troubleshooting of your connector deployment. +Two main scripts are provided: + +* 1/ Save all pods logs from a EKS cluster namespace using `kubectl` to your local deployment machine under `./podLogs/` [in this repository structure](./podLogs/) +The script `kubectlLogsFromNamespace.sh` [link](../scripts/kubectlLogsFromNamespace.sh) runs a local process in your deployment machine to poll logs from all currently running pods under a namespace from your cluster and save locally for further analysis. +It can be manually modified to change the desired namespace to be analyzed + +```shell +#!/bin/bash + +NAMESPACE="ips" +``` + +and the corresponding polling period by changing the values in the script file before running it + +```shell + # Sleep for a few seconds before checking for new logs and pods again + sleep 3 +done +``` + +* 2/ Delete all currently running pods from an EKS cluster namespace using `kubectl` +The script `deleteAllPodsFromNamespace.sh` [link](../scripts/deleteAllPodsFromNamespace.sh) runs a local process in your deployment machine to force delete long-lived running pods from a failed deployment. +It can be manually modified to change the desired namespace to be analyzed + +```shell +#!/bin/bash + +# Set the fixed namespace +namespace="ips" +``` \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/.gitignore b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/.gitignore new file mode 100644 index 0000000..abaa3d2 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/.gitignore @@ -0,0 +1,10 @@ +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +.DS_Store + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CHANGELOG.md b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CODE_OF_CONDUCT.md b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5b627cf --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CONTRIBUTING.md b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CONTRIBUTING.md new file mode 100644 index 0000000..c4b6a1c --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + + +## Contributing via Pull Requests +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: + +1. You are working against the latest source on the *main* branch. +2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your time to be wasted. + +To send us a pull request, please: + +1. Fork the repository. +2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +3. Ensure local tests pass. +4. Commit to your fork using clear commit messages. +5. Send us a pull request, answering any default questions in the pull request interface. +6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). + + +## Finding contributions to work on +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. + + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. + + +## Licensing + +See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/LICENSE b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/LICENSE new file mode 100644 index 0000000..09951d9 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/LICENSE @@ -0,0 +1,17 @@ +MIT No Attribution + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/README.md b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/README.md new file mode 100644 index 0000000..57e830a --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/README.md @@ -0,0 +1,30 @@ +## Garnet Framework + +> Garnet Framework replaces the Smart Territory Framework + +__Explore the [documentation website of Garnet Framework](https://garnet-framework.dev/docs) to get started.__ + +Garnet is an open-source framework for building scalable, reliable and interoperable platforms leveraging open standards, FIWARE open source technology and AWS Cloud services. + +It supports the development and integration of smart and efficient solutions across multiple domains such as Smart Cities, Regions and Campuses, Energy and Utilities, Agriculture, Smart Building, Automotive and Manufacturing. + +Garnet facilitates the creation and connection of smart solutions. It provides you, off-the-shelf, geospatial and subscriptions capabilities, datalake, device management and digital twin capabilities. It helps you avoid vendor lock-in and break silos. It makes it easier to build knowledge graphs, bring data together into one single space from heterogeneous sources, both internal and external, and extract actionable insights from your data. + +Using Garnet, you can easily integrate existing solutions and add new capabilities and modules over time. + +There are only three types of components in a platform and solutions built using Garnet: Data Producers, Data Consumers and Garnet as the core platform. + +Garnet is the core component of your platform. It all starts with it. + +## Getting Started + +Explore the [documentation website of Garnet Framework](https://garnet-framework.dev/docs) to get started. + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## License + +This library is licensed under the MIT-0 License. See the LICENSE file. + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/bin/garnet.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/bin/garnet.ts new file mode 100644 index 0000000..caf4fb1 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/bin/garnet.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import { App } from 'aws-cdk-lib'; +import { GarnetStack } from '../lib/garnet-stack'; +import { Parameters } from '../parameters'; + +const app = new App(); + +new GarnetStack(app, 'Garnet', { + stackName: 'Garnet', + description: 'Garnet Framework is an open-source framework for building scalable, reliable and interoperable platforms - (uksb-1tupboc26)', + env: { region: Parameters.aws_region } +}) \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/cdk.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/cdk.json new file mode 100644 index 0000000..d2aaa0e --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/cdk.json @@ -0,0 +1,63 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/garnet.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/context.jsonld b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/context.jsonld new file mode 100644 index 0000000..6dcb932 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/context.jsonld @@ -0,0 +1,1734 @@ +{ + "@context": { + "0": "https://uri.fiware.org/ns/data-models#0", + "1": "https://uri.fiware.org/ns/data-models#1", + "2": "https://uri.fiware.org/ns/data-models#2", + "3": "https://uri.fiware.org/ns/data-models#3", + "37": "https://uri.fiware.org/ns/data-models#37", + "37,38": "https://uri.fiware.org/ns/data-models#37,38", + "37,38,39": "https://uri.fiware.org/ns/data-models#37,38,39", + "37,39": "https://uri.fiware.org/ns/data-models#37,39", + "38": "https://uri.fiware.org/ns/data-models#38", + "38,39": "https://uri.fiware.org/ns/data-models#38,39", + "39": "https://uri.fiware.org/ns/data-models#39", + "4": "https://uri.fiware.org/ns/data-models#4", + "5": "https://uri.fiware.org/ns/data-models#5", + "6": "https://uri.fiware.org/ns/data-models#6", + "7": "https://uri.fiware.org/ns/data-models#7", + "@vocab": "https://uri.etsi.org/ngsi-ld/default-context/", + "AeroAllergenObserved": "https://uri.fiware.org/ns/data-models#AeroAllergenObserved", + "AgriApp": "https://uri.fiware.org/ns/data-models#AgriApp", + "AgriCrop": "https://uri.fiware.org/ns/data-models#AgriCrop", + "AgriFarm": "https://uri.fiware.org/ns/data-models#AgriFarm", + "AgriGreenhouse": "https://uri.fiware.org/ns/data-models#AgriGreenhouse", + "AgriParcel": "https://uri.fiware.org/ns/data-models#AgriParcel", + "AgriParcelOperation": "https://uri.fiware.org/ns/data-models#AgriParcelOperation", + "AgriParcelRecord": "https://uri.fiware.org/ns/data-models#AgriParcelRecord", + "AgriPest": "https://uri.fiware.org/ns/data-models#AgriPest", + "AgriProductType": "https://uri.fiware.org/ns/data-models#AgriProductType", + "AgriSoil": "https://uri.fiware.org/ns/data-models#AgriSoil", + "AirQualityObserved": "https://uri.fiware.org/ns/data-models#AirQualityObserved", + "Alert": "https://uri.fiware.org/ns/data-models#Alert", + "Animal": "https://w3id.org/def/saref4agri#Animal", + "ArrivalEstimation": "https://uri.fiware.org/ns/data-models#ArrivalEstimation", + "Beach": "https://uri.fiware.org/ns/data-models#Beach", + "BikeHireDockingStation": "https://uri.fiware.org/ns/data-models#BikeHireDockingStation", + "Building": "https://uri.fiware.org/ns/data-models#Building", + "BuildingOperation": "https://uri.fiware.org/ns/data-models#BuildingOperation", + "ByBankTransferInAdvance": "http://purl.org/goodrelations/v1#ByBankTransferInAdvance", + "ByInvoice": "http://purl.org/goodrelations/v1#ByInvoice", + "C0": "https://uri.fiware.org/ns/data-models#C0", + "C1": "https://uri.fiware.org/ns/data-models#C1", + "C2": "https://uri.fiware.org/ns/data-models#C2", + "CCS_SAE": "https://uri.fiware.org/ns/data-models#CCS_SAE", + "CHAdeMO": "https://uri.fiware.org/ns/data-models#CHAdeMO", + "COD": "http://purl.org/goodrelations/v1#COD", + "Caravan_Mains_Socket": "https://uri.fiware.org/ns/data-models#Caravan_Mains_Socket", + "Cash": "http://purl.org/goodrelations/v1#Cash", + "CheckInAdvance": "http://purl.org/goodrelations/v1#CheckInAdvance", + "Chla": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/Chla", + "Cl-": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/Cl-", + "ContextSourceNotification": "https://uri.etsi.org/ngsi-ld/ContextSourceNotification", + "ContextSourceRegistration": "https://uri.etsi.org/ngsi-ld/ContextSourceRegistration", + "CrowdFlowObserved": "https://uri.fiware.org/ns/data-models#CrowdFlowObserved", + "Date": "https://uri.etsi.org/ngsi-ld/Date", + "DateTime": "https://uri.etsi.org/ngsi-ld/DateTime", + "Device": "https://uri.fiware.org/ns/data-models#Device", + "DeviceModel": "https://uri.fiware.org/ns/data-models#DeviceModel", + "DirectDebit": "http://purl.org/goodrelations/v1#DirectDebit", + "Dual_CHAdeMO": "https://uri.fiware.org/ns/data-models#Dual_CHAdeMO", + "Dual_J-1772": "https://uri.fiware.org/ns/data-models#Dual_J-1772", + "Dual_Mennekes": "https://uri.fiware.org/ns/data-models#Dual_Mennekes", + "E0": "https://uri.fiware.org/ns/data-models#E0", + "E1": "https://uri.fiware.org/ns/data-models#E1", + "E2": "https://uri.fiware.org/ns/data-models#E2", + "E9": "https://uri.fiware.org/ns/data-models#E9", + "EVChargingStation": "https://uri.fiware.org/ns/data-models#EVChargingStation", + "FlowerBed": "https://uri.fiware.org/ns/data-models#FlowerBed", + "Garden": "https://uri.fiware.org/ns/data-models#Garden", + "GeoProperty": "https://uri.etsi.org/ngsi-ld/GeoProperty", + "GoogleCheckout": "http://purl.org/goodrelations/v1#GoogleCheckout", + "GreenspaceRecord": "https://uri.fiware.org/ns/data-models#GreenspaceRecord", + "GtfsAccessPoint": "https://uri.fiware.org/ns/data-models#GtfsAccessPoint", + "GtfsAgency": "https://uri.fiware.org/ns/data-models#GtfsAgency", + "GtfsCalendarDateRule": "https://uri.fiware.org/ns/data-models#GtfsCalendarDateRule", + "GtfsCalendarRule": "https://uri.fiware.org/ns/data-models#GtfsCalendarRule", + "GtfsFrequency": "https://uri.fiware.org/ns/data-models#GtfsFrequency", + "GtfsRoute": "https://uri.fiware.org/ns/data-models#GtfsRoute", + "GtfsService": "https://uri.fiware.org/ns/data-models#GtfsService", + "GtfsShape": "https://uri.fiware.org/ns/data-models#GtfsShape", + "GtfsStation": "https://uri.fiware.org/ns/data-models#GtfsStation", + "GtfsStop": "https://uri.fiware.org/ns/data-models#GtfsStop", + "GtfsStopTime": "https://uri.fiware.org/ns/data-models#GtfsStopTime", + "GtfsTransferRule": "https://uri.fiware.org/ns/data-models#GtfsTransferRule", + "GtfsTrip": "https://uri.fiware.org/ns/data-models#GtfsTrip", + "HPS": "https://uri.fiware.org/ns/data-models#HPS", + "J-1772": "https://uri.fiware.org/ns/data-models#J-1772", + "KeyPerformanceIndicator": "https://uri.fiware.org/ns/data-models#KeyPerformanceIndicator", + "LED": "https://uri.fiware.org/ns/data-models#LED", + "LPS": "https://uri.fiware.org/ns/data-models#LPS", + "LineString": "https://purl.org/geojson/vocab#LineString", + "Mennekes": "https://uri.fiware.org/ns/data-models#Mennekes", + "MultiLineString": "https://purl.org/geojson/vocab#MultiLineString", + "MultiPoint": "https://purl.org/geojson/vocab#MultiPoint", + "MultiPolygon": "https://purl.org/geojson/vocab#MultiPolygon", + "Museum": "https://uri.fiware.org/ns/data-models#Museum", + "NH3": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/NH3", + "NH4": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/NH4", + "NO3": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/NO3", + "NoiseLevelObserved": "https://uri.fiware.org/ns/data-models#NoiseLevelObserved", + "Notification": "https://uri.etsi.org/ngsi-ld/Notification", + "O2": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/O2", + "OffStreetParking": "https://uri.fiware.org/ns/data-models#OffStreetParking", + "OnStreetParking": "https://uri.fiware.org/ns/data-models#OnStreetParking", + "Open311ServiceRequest": "https://uri.fiware.org/ns/data-models#Open311ServiceRequest", + "Open311ServiceType": "https://uri.fiware.org/ns/data-models#Open311ServiceType", + "Other": "https://uri.fiware.org/ns/data-models#Other", + "PC": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/PC", + "PE": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/PE", + "ParkingAccess": "https://uri.fiware.org/ns/data-models#ParkingAccess", + "ParkingGroup": "https://uri.fiware.org/ns/data-models#ParkingGroup", + "ParkingSpot": "https://uri.fiware.org/ns/data-models#ParkingSpot", + "PayPal": "http://purl.org/goodrelations/v1#PayPal", + "PaySwarm": "http://purl.org/goodrelations/v1#PaySwarm", + "Point": "https://purl.org/geojson/vocab#Point", + "PointOfInterest": "https://uri.fiware.org/ns/data-models#PointOfInterest", + "Polygon": "https://purl.org/geojson/vocab#Polygon", + "Property": "https://uri.etsi.org/ngsi-ld/Property", + "Q-Quality": "https://uri.fiware.org/ns/data-models#Q-Quality", + "Relationship": "https://uri.etsi.org/ngsi-ld/Relationship", + "Road": "https://uri.fiware.org/ns/data-models#Road", + "RoadSegment": "https://uri.fiware.org/ns/data-models#RoadSegment", + "SmartPointOfInteraction": "https://uri.fiware.org/ns/data-models#SmartPointOfInteraction", + "SmartSpot": "https://uri.fiware.org/ns/data-models#SmartSpot", + "Streetlight": "https://uri.fiware.org/ns/data-models#Streetlight", + "StreetlightControlCabinet": "https://uri.fiware.org/ns/data-models#StreetlightControlCabinet", + "StreetlightGroup": "https://uri.fiware.org/ns/data-models#StreetlightGroup", + "StreetlightModel": "https://uri.fiware.org/ns/data-models#StreetlightModel", + "Subscription": "https://uri.etsi.org/ngsi-ld/Subscription", + "TBD": "https://uri.fiware.org/ns/data-models#TBD", + "TemporalProperty": "https://uri.etsi.org/ngsi-ld/TemporalProperty", + "Tesla": "https://uri.fiware.org/ns/data-models#Tesla", + "ThreePhaseAcMeasurement": "https://uri.fiware.org/ns/data-models#ThreePhaseAcMeasurement", + "Time": "https://uri.etsi.org/ngsi-ld/Time", + "TimeInstant": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/TimeInstant", + "TrafficFlowObserved": "https://uri.fiware.org/ns/data-models#TrafficFlowObserved", + "Type2": "https://uri.fiware.org/ns/data-models#Type2", + "Type3": "https://uri.fiware.org/ns/data-models#Type3", + "UserActivity": "https://uri.fiware.org/ns/data-models#UserActivity", + "UserContext": "https://uri.fiware.org/ns/data-models#UserContext", + "Vehicle": "https://uri.fiware.org/ns/data-models#Vehicle", + "VehicleModel": "https://uri.fiware.org/ns/data-models#VehicleModel", + "Wall_Euro": "https://uri.fiware.org/ns/data-models#Wall_Euro", + "WasteContainer": "https://uri.fiware.org/ns/data-models#WasteContainer", + "WasteContainerIsle": "https://uri.fiware.org/ns/data-models#WasteContainerIsle", + "WasteContainerModel": "https://uri.fiware.org/ns/data-models#WasteContainerModel", + "WaterQualityObserved": "https://uri.fiware.org/ns/data-models#WaterQualityObserved", + "WeatherForecast": "https://uri.fiware.org/ns/data-models#WeatherForecast", + "WeatherObserved": "https://uri.fiware.org/ns/data-models#WeatherObserved", + "aborted": "https://uri.fiware.org/ns/data-models#aborted", + "abs": "https://uri.fiware.org/ns/data-models#abs", + "acPowerInput": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/acPowerInput", + "acPowerOutput": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/acPowerOutput", + "accept": "https://uri.etsi.org/ngsi-ld/accept", + "acceptedPaymentMethod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/acceptedPaymentMethod", + "accessType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/accessType", + "accessforDisabled": "https://uri.fiware.org/ns/data-models#accessforDisabled", + "acropolis": "https://uri.fiware.org/ns/data-models#acropolis", + "actionable": "https://uri.fiware.org/ns/data-models#actionable", + "active": "https://uri.fiware.org/ns/data-models#active", + "activeEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activeEnergyExport", + "activeEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activeEnergyImport", + "activePower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activePower", + "activePowerR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activePowerR", + "activePowerS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activePowerS", + "activePowerT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activePowerT", + "activeProgramId": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activeProgramId", + "activityType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/activityType", + "actuationHours": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/actuationHours", + "additionalIntervalPrice": "https://uri.fiware.org/ns/data-models#additionalIntervalPrice", + "address": "https://schema.org/address", + "adequate": "https://uri.fiware.org/ns/data-models#adequate", + "aggregateRating": "https://schema.org/aggregateRating", + "aggregatedData": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/aggregatedData", + "agriculturalVehicle": "https://uri.fiware.org/ns/data-models#agriculturalVehicle", + "agriculture": "https://uri.fiware.org/ns/data-models#agriculture", + "agroVocConcept": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/agroVocConcept", + "airPollution": "https://uri.fiware.org/ns/data-models#airPollution", + "airQualityIndex": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/airQualityIndex", + "airQualityLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/airQualityLevel", + "airbag": "https://uri.fiware.org/ns/data-models#airbag", + "airportTerminal": "https://uri.fiware.org/ns/data-models#airportTerminal", + "alarm": "https://uri.fiware.org/ns/data-models#alarm", + "alcazaba": "https://uri.fiware.org/ns/data-models#alcazaba", + "alcazar": "https://uri.fiware.org/ns/data-models#alcazar", + "alertSource": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/alertSource", + "allergenRisk": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/allergenRisk", + "allowedVehicleType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/allowedVehicleType", + "almostEmpty": "https://uri.fiware.org/ns/data-models#almostEmpty", + "almostFull": "https://uri.fiware.org/ns/data-models#almostFull", + "alternateName": "https://schema.org/alternateName", + "aluminium": "https://uri.fiware.org/ns/data-models#aluminium", + "always-ON": "https://uri.fiware.org/ns/data-models#always-ON", + "ambulance": "https://uri.fiware.org/ns/data-models#ambulance", + "amperage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/amperage", + "amphitheatre": "https://uri.fiware.org/ns/data-models#amphitheatre", + "annotations": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/annotations", + "announcedUrl": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/announcedUrl", + "announcementPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/announcementPeriod", + "annualPayment": "https://uri.fiware.org/ns/data-models#annualPayment", + "anyVehicle": "https://uri.fiware.org/ns/data-models#anyVehicle", + "apartments": "https://uri.fiware.org/ns/data-models#apartments", + "apparentEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/apparentEnergyExport", + "apparentEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/apparentEnergyImport", + "apparentPower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/apparentPower", + "applicationUrl": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/applicationUrl", + "appliedArts": "https://uri.fiware.org/ns/data-models#appliedArts", + "appliesOn": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/appliesOn", + "aqueduct": "https://uri.fiware.org/ns/data-models#aqueduct", + "arcade": "https://uri.fiware.org/ns/data-models#arcade", + "arch": "https://uri.fiware.org/ns/data-models#arch", + "archaeology": "https://uri.fiware.org/ns/data-models#archaeology", + "archeologicalSite": "https://uri.fiware.org/ns/data-models#archeologicalSite", + "architecture": "https://uri.fiware.org/ns/data-models#architecture", + "area": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/area", + "areaCovered": "https://uri.fiware.org/ns/data-models#areaCovered", + "areaSeperatedFromSurroundings": "https://uri.fiware.org/ns/data-models#areaSeperatedFromSurroundings", + "areaServed": "https://schema.org/areaServed", + "arrivalEstimationUpdate": { + "@id": "https://uri.fiware.org/ns/data-models#arrivalEstimationUpdate", + "@type": "https://uri.etsi.org/ngsi-ld/DateTime" + }, + "arrivalTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/arrivalTime", + "artPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/artPeriod", + "as": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/as", + "assault": "https://uri.fiware.org/ns/data-models#assault", + "asthmaAttack": "https://uri.fiware.org/ns/data-models#asthmaAttack", + "astronomicalClock": "https://uri.fiware.org/ns/data-models#astronomicalClock", + "atmosphericPressure": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/atmosphericPressure", + "attributeName": "https://uri.etsi.org/ngsi-ld/attributeName", + "attributes": { + "@id": "https://uri.etsi.org/ngsi-ld/attributes", + "@type": "@vocab" + }, + "audioguide": "https://uri.fiware.org/ns/data-models#audioguide", + "auditory": "https://uri.fiware.org/ns/data-models#auditory", + "autogas": "https://uri.fiware.org/ns/data-models#autogas", + "automatedParkingGarage": "https://uri.fiware.org/ns/data-models#automatedParkingGarage", + "automatic": "https://uri.fiware.org/ns/data-models#automatic", + "automaticParkingGuidance": "https://uri.fiware.org/ns/data-models#automaticParkingGuidance", + "autonomyTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/autonomyTime", + "auxiliaryServices": "https://uri.fiware.org/ns/data-models#auxiliaryServices", + "availability": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/availability", + "availableBikeNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/availableBikeNumber", + "availableCapacity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/availableCapacity", + "availableSpotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/availableSpotNumber", + "avalancheRisk": "https://uri.fiware.org/ns/data-models#avalancheRisk", + "averageCrowdSpeed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageCrowdSpeed", + "averageGapDistance": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageGapDistance", + "averageHeadwayTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageHeadwayTime", + "averageSpotLength": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageSpotLength", + "averageSpotWidth": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageSpotWidth", + "averageVehicleLength": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageVehicleLength", + "averageVehicleSpeed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/averageVehicleSpeed", + "backCamera": "https://uri.fiware.org/ns/data-models#backCamera", + "backward": "https://uri.fiware.org/ns/data-models#backward", + "bacteria": "https://uri.fiware.org/ns/data-models#bacteria", + "bad": "https://uri.fiware.org/ns/data-models#bad", + "bakehouse": "https://uri.fiware.org/ns/data-models#bakehouse", + "balancing": "https://uri.fiware.org/ns/data-models#balancing", + "bandalized": "https://uri.fiware.org/ns/data-models#bandalized", + "barn": "https://uri.fiware.org/ns/data-models#barn", + "barrierAccess": "https://uri.fiware.org/ns/data-models#barrierAccess", + "baseDemand": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/baseDemand", + "basilica": "https://uri.fiware.org/ns/data-models#basilica", + "batteries": "https://uri.fiware.org/ns/data-models#batteries", + "batteryLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/batteryLevel", + "bbox": { + "@container": "@list", + "@id": "geojson:bbox" + }, + "beachType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/beachType", + "belongsTo": { + "@id": "https://uri.fiware.org/ns/data-models#belongsTo", + "@type": "@id" + }, + "bicycle": "https://uri.fiware.org/ns/data-models#bicycle", + "bikeParking": "https://uri.fiware.org/ns/data-models#bikeParking", + "bimonthly": "https://uri.fiware.org/ns/data-models#bimonthly", + "binTrolley": "https://uri.fiware.org/ns/data-models#binTrolley", + "biodiesel": "https://uri.fiware.org/ns/data-models#biodiesel", + "biomassPowerGenerated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/biomassPowerGenerated", + "birthdate": { + "@id": "https://uri.fiware.org/ns/data-models#birthdate", + "@type": "https://uri.etsi.org/ngsi-ld/DateTime" + }, + "biweekly": "https://uri.fiware.org/ns/data-models#biweekly", + "blackSand": "https://uri.fiware.org/ns/data-models#blackSand", + "block": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/block", + "blueFlag": "https://uri.fiware.org/ns/data-models#blueFlag", + "bluetoothChannel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/bluetoothChannel", + "boat": "https://uri.fiware.org/ns/data-models#boat", + "bollard": "https://uri.fiware.org/ns/data-models#bollard", + "bootingUp": "https://uri.fiware.org/ns/data-models#bootingUp", + "borehole": "https://uri.fiware.org/ns/data-models#borehole", + "botanical": "https://uri.fiware.org/ns/data-models#botanical", + "brandName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/brandName", + "breed": "https://uri.fiware.org/ns/data-models#breed", + "bridge": "https://uri.fiware.org/ns/data-models#bridge", + "broken": "https://uri.fiware.org/ns/data-models#broken", + "brokenLantern": "https://uri.fiware.org/ns/data-models#brokenLantern", + "buildingFire": "https://uri.fiware.org/ns/data-models#buildingFire", + "buildingType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/buildingType", + "bulkCoeff": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/bulkCoeff", + "bulkReactionCoefficient": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/bulkReactionCoefficient", + "bullfighting": "https://uri.fiware.org/ns/data-models#bullfighting", + "bullfightingRing": "https://uri.fiware.org/ns/data-models#bullfightingRing", + "bumpedPatient": "https://uri.fiware.org/ns/data-models#bumpedPatient", + "bungalow": "https://uri.fiware.org/ns/data-models#bungalow", + "bunker": "https://uri.fiware.org/ns/data-models#bunker", + "burialMound": "https://uri.fiware.org/ns/data-models#burialMound", + "burning": "https://uri.fiware.org/ns/data-models#burning", + "bus": "https://uri.fiware.org/ns/data-models#bus", + "businessTarget": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/businessTarget", + "c6h6": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/c6h6", + "cabin": "https://uri.fiware.org/ns/data-models#cabin", + "cableCarStation": "https://uri.fiware.org/ns/data-models#cableCarStation", + "cafeteria": "https://uri.fiware.org/ns/data-models#cafeteria", + "calculatedBy": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/calculatedBy", + "calculationFormula": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/calculationFormula", + "calculationFrequency": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/calculationFrequency", + "calculationMethod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/calculationMethod", + "calculationPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/calculationPeriod", + "calmWaters": "https://uri.fiware.org/ns/data-models#calmWaters", + "calvedBy": { + "@id": "https://uri.fiware.org/ns/data-models#calvedBy", + "@type": "@id" + }, + "campground": "https://uri.fiware.org/ns/data-models#campground", + "cancelled": "https://uri.fiware.org/ns/data-models#cancelled", + "capacity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/capacity", + "car": "https://uri.fiware.org/ns/data-models#car", + "carAccident": "https://uri.fiware.org/ns/data-models#carAccident", + "carSharing": "https://uri.fiware.org/ns/data-models#carSharing", + "carStopped": "https://uri.fiware.org/ns/data-models#carStopped", + "carWithCaravan": "https://uri.fiware.org/ns/data-models#carWithCaravan", + "carWithTrailer": "https://uri.fiware.org/ns/data-models#carWithTrailer", + "carWrongDirection": "https://uri.fiware.org/ns/data-models#carWrongDirection", + "caravan": "https://uri.fiware.org/ns/data-models#caravan", + "cargoTransport": "https://uri.fiware.org/ns/data-models#cargoTransport", + "cargoVolume": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cargoVolume", + "cargoWeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cargoWeight", + "carport": "https://uri.fiware.org/ns/data-models#carport", + "carports": "https://uri.fiware.org/ns/data-models#carports", + "cartuja": "https://uri.fiware.org/ns/data-models#cartuja", + "cashMachine": "https://uri.fiware.org/ns/data-models#cashMachine", + "castle": "https://uri.fiware.org/ns/data-models#castle", + "castro": "https://uri.fiware.org/ns/data-models#castro", + "catacombs": "https://uri.fiware.org/ns/data-models#catacombs", + "category": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/category", + "cathedral": "https://uri.fiware.org/ns/data-models#cathedral", + "cathedralMuseum": "https://uri.fiware.org/ns/data-models#cathedralMuseum", + "cavesAndTouristicMines": "https://uri.fiware.org/ns/data-models#cavesAndTouristicMines", + "cctv": "https://uri.fiware.org/ns/data-models#cctv", + "cd": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cd", + "cdom": "https://uri.fiware.org/ns/data-models#cdom", + "centralIsland": "https://uri.fiware.org/ns/data-models#centralIsland", + "ceramics": "https://uri.fiware.org/ns/data-models#ceramics", + "chapel": "https://uri.fiware.org/ns/data-models#chapel", + "chargeType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/chargeType", + "chinese": "https://uri.fiware.org/ns/data-models#chinese", + "church": "https://uri.fiware.org/ns/data-models#church", + "cicleLife": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cicleLife", + "cinema": "https://uri.fiware.org/ns/data-models#cinema", + "circuit": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/circuit", + "circular": "https://uri.fiware.org/ns/data-models#circular", + "circus": "https://uri.fiware.org/ns/data-models#circus", + "civic": "https://uri.fiware.org/ns/data-models#civic", + "civilDisorder": "https://uri.fiware.org/ns/data-models#civilDisorder", + "civilEngineering": "https://uri.fiware.org/ns/data-models#civilEngineering", + "cleaningServices": "https://uri.fiware.org/ns/data-models#cleaningServices", + "cleaningTrolley": "https://uri.fiware.org/ns/data-models#cleaningTrolley", + "cloakRoom": "https://uri.fiware.org/ns/data-models#cloakRoom", + "cloister": "https://uri.fiware.org/ns/data-models#cloister", + "closed": "https://uri.fiware.org/ns/data-models#closed", + "closedAbnormal": "https://uri.fiware.org/ns/data-models#closedAbnormal", + "cng": "https://uri.fiware.org/ns/data-models#cng", + "co": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/co", + "co-creation": "https://uri.fiware.org/ns/data-models#co-creation", + "co2": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/co2", + "coachStation": "https://uri.fiware.org/ns/data-models#coachStation", + "coastalEvent": "https://uri.fiware.org/ns/data-models#coastalEvent", + "code": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/code", + "coldWave": "https://uri.fiware.org/ns/data-models#coldWave", + "color": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/color", + "colorRenderingIndex": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/colorRenderingIndex", + "colorTemperature": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/colorTemperature", + "columnBrandName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/columnBrandName", + "columnColor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/columnColor", + "columnIssue": "https://uri.fiware.org/ns/data-models#columnIssue", + "columnMadeOf": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/columnMadeOf", + "columnManufacturerName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/columnManufacturerName", + "columnModelName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/columnModelName", + "commercial": "https://uri.fiware.org/ns/data-models#commercial", + "commercial_supply": "https://uri.fiware.org/ns/data-models#commercial_supply", + "community": "https://uri.fiware.org/ns/data-models#community", + "compliantWith": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/compliantWith", + "concrete": "https://uri.fiware.org/ns/data-models#concrete", + "conductance": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/conductance", + "conductivity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/conductivity", + "conferenceRoom": "https://uri.fiware.org/ns/data-models#conferenceRoom", + "configuration": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/configuration", + "congested": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/congested", + "conservatory": "https://uri.fiware.org/ns/data-models#conservatory", + "construction": "https://uri.fiware.org/ns/data-models#construction", + "constructionOrMaintenanceVehicle": "https://uri.fiware.org/ns/data-models#constructionOrMaintenanceVehicle", + "contactPoint": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/contactPoint", + "containedInPlace": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/containedInPlace", + "containerFix": "https://uri.fiware.org/ns/data-models#containerFix", + "contemporaryArt": "https://uri.fiware.org/ns/data-models#contemporaryArt", + "controlledAsset": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/controlledAsset", + "controlledProperty": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/controlledProperty", + "controllingMethod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/controllingMethod", + "convent": "https://uri.fiware.org/ns/data-models#convent", + "conventionCentre": "https://uri.fiware.org/ns/data-models#conventionCentre", + "coordinates": { + "@container": "@list", + "@id": "geojson:coordinates" + }, + "copyMachineOrService": "https://uri.fiware.org/ns/data-models#copyMachineOrService", + "cosPhi": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cosPhi", + "coverageRadius": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/coverageRadius", + "covered": "https://uri.fiware.org/ns/data-models#covered", + "cow": "https://uri.fiware.org/ns/data-models#cow", + "cowshed": "https://uri.fiware.org/ns/data-models#cowshed", + "createdAt": { + "@id": "https://uri.etsi.org/ngsi-ld/createdAt", + "@type": "DateTime" + }, + "critical": "https://uri.fiware.org/ns/data-models#critical", + "cropNutrition": "https://uri.fiware.org/ns/data-models#cropNutrition", + "cropProtection": "https://uri.fiware.org/ns/data-models#cropProtection", + "cropStatus": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cropStatus", + "cropVariety": "https://uri.fiware.org/ns/data-models#cropVariety", + "csf": "https://uri.etsi.org/ngsi-ld/csf", + "cupboardMadeOf": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/cupboardMadeOf", + "currenciesAccepted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/currenciesAccepted", + "current": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/current", + "currentStanding": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/currentStanding", + "curveType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/curveType", + "daily": "https://uri.fiware.org/ns/data-models#daily", + "dailyLight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dailyLight", + "data": "https://uri.etsi.org/ngsi-ld/data", + "dataProvider": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dataProvider", + "datasetId": { + "@id": "https://uri.etsi.org/ngsi-ld/datasetId", + "@type": "@id" + }, + "dateActivityEnded": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateActivityEnded", + "dateActivityStarted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateActivityStarted", + "dateCreated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateCreated", + "dateEnergyMeteringStarted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateEnergyMeteringStarted", + "dateExpires": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateExpires", + "dateFinished": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateFinished", + "dateFirstUsed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateFirstUsed", + "dateInstalled": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateInstalled", + "dateIssued": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateIssued", + "dateLastCalibration": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastCalibration", + "dateLastCleaning": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastCleaning", + "dateLastEmptying": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastEmptying", + "dateLastLampChange": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastLampChange", + "dateLastProgramming": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastProgramming", + "dateLastSwitchingOff": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastSwitchingOff", + "dateLastSwitchingOn": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastSwitchingOn", + "dateLastValueReported": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastValueReported", + "dateLastWatering": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateLastWatering", + "dateManufactured": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateManufactured", + "dateMeteringStarted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateMeteringStarted", + "dateModified": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateModified", + "dateNextCalculation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateNextCalculation", + "dateObserved": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateObserved", + "dateObservedFrom": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateObservedFrom", + "dateObservedTo": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateObservedTo", + "dateRetrieved": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateRetrieved", + "dateServiceStarted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateServiceStarted", + "dateStarted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateStarted", + "dateVehicleFirstRegistered": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dateVehicleFirstRegistered", + "day-LOW": "https://uri.fiware.org/ns/data-models#day-LOW", + "day-OFF": "https://uri.fiware.org/ns/data-models#day-OFF", + "day-ON": "https://uri.fiware.org/ns/data-models#day-ON", + "dayMaximum": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dayMaximum", + "dayMinimum": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dayMinimum", + "dcPowerInput": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dcPowerInput", + "dcPowerOutput": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dcPowerOutput", + "decorativeArts": "https://uri.fiware.org/ns/data-models#decorativeArts", + "defectiveLamp": "https://uri.fiware.org/ns/data-models#defectiveLamp", + "defibrillator": "https://uri.fiware.org/ns/data-models#defibrillator", + "demandCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/demandCategory", + "demandeCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/demandeCategory", + "departureTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/departureTime", + "depth": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/depth", + "description": "https://uri.etsi.org/ngsi-ld/description", + "detached": "https://uri.fiware.org/ns/data-models#detached", + "detail": "https://uri.etsi.org/ngsi-ld/detail", + "deviceClass": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/deviceClass", + "deviceState": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/deviceState", + "dewPoint": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dewPoint", + "diameter": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/diameter", + "diesel": "https://uri.fiware.org/ns/data-models#diesel", + "digester": "https://uri.fiware.org/ns/data-models#digester", + "diocesanMuseum": "https://uri.fiware.org/ns/data-models#diocesanMuseum", + "direction": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/direction", + "directional": "https://uri.fiware.org/ns/data-models#directional", + "disabledRamp": "https://uri.fiware.org/ns/data-models#disabledRamp", + "displacementPowerFactor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/displacementPowerFactor", + "distanceTravelled": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/distanceTravelled", + "documentation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/documentation", + "dog": "https://uri.fiware.org/ns/data-models#dog", + "dolmen": "https://uri.fiware.org/ns/data-models#dolmen", + "dormitory": "https://uri.fiware.org/ns/data-models#dormitory", + "drainFlow": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/drainFlow", + "dropOff": "https://uri.fiware.org/ns/data-models#dropOff", + "dropOffMechanical": "https://uri.fiware.org/ns/data-models#dropOffMechanical", + "dropOffType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/dropOffType", + "dropOffWithValet": "https://uri.fiware.org/ns/data-models#dropOffWithValet", + "dropped": "https://uri.fiware.org/ns/data-models#dropped", + "dumpingStation": "https://uri.fiware.org/ns/data-models#dumpingStation", + "dumpster": "https://uri.fiware.org/ns/data-models#dumpster", + "earthquake": "https://uri.fiware.org/ns/data-models#earthquake", + "echelonParking": "https://uri.fiware.org/ns/data-models#echelonParking", + "effectiveSince": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/effectiveSince", + "efficCurve": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/efficCurve", + "electric": "https://uri.fiware.org/ns/data-models#electric", + "electricChargingStation": "https://uri.fiware.org/ns/data-models#electricChargingStation", + "electricityConsumption": "https://uri.fiware.org/ns/data-models#electricityConsumption", + "electronics": "https://uri.fiware.org/ns/data-models#electronics", + "elevation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/elevation", + "elevator": "https://uri.fiware.org/ns/data-models#elevator", + "elliptic": "https://uri.fiware.org/ns/data-models#elliptic", + "email": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/email", + "emitterCoefficient": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/emitterCoefficient", + "employeePermit": "https://uri.fiware.org/ns/data-models#employeePermit", + "empty": "https://uri.fiware.org/ns/data-models#empty", + "end": { + "@id": "https://uri.etsi.org/ngsi-ld/end", + "@type": "DateTime" + }, + "endDate": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/endDate", + "endKilometer": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/endKilometer", + "endPoint": "https://uri.fiware.org/ns/data-models#endPoint", + "endTime": { + "@id": "https://uri.etsi.org/ngsi-ld/endTime", + "@type": "DateTime" + }, + "endedAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/endedAt", + "endpoint": "https://uri.etsi.org/ngsi-ld/endpoint", + "endsAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/endsAt", + "energy": "https://uri.fiware.org/ns/data-models#energy", + "energyConsumed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/energyConsumed", + "energyCost": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/energyCost", + "energyLimitationClass": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/energyLimitationClass", + "energyPattern": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/energyPattern", + "energyPrice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/energyPrice", + "english": "https://uri.fiware.org/ns/data-models#english", + "entertainment": "https://uri.fiware.org/ns/data-models#entertainment", + "entities": "https://uri.etsi.org/ngsi-ld/entities", + "entityId": { + "@id": "https://uri.etsi.org/ngsi-ld/entityId", + "@type": "@id" + }, + "environment": "https://uri.fiware.org/ns/data-models#environment", + "eolicPowerGenerated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/eolicPowerGenerated", + "error": "https://uri.etsi.org/ngsi-ld/error", + "errors": "https://uri.etsi.org/ngsi-ld/errors", + "ethanol": "https://uri.fiware.org/ns/data-models#ethanol", + "ethnology": "https://uri.fiware.org/ns/data-models#ethnology", + "eventNotification": "https://uri.fiware.org/ns/data-models#eventNotification", + "eventParking": "https://uri.fiware.org/ns/data-models#eventParking", + "exactTimes": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/exactTimes", + "exceptionType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/exceptionType", + "exhibitonCentre": "https://uri.fiware.org/ns/data-models#exhibitonCentre", + "expires": { + "@id": "https://uri.etsi.org/ngsi-ld/expires", + "@type": "DateTime" + }, + "extCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/extCategory", + "externalSecurity": "https://uri.fiware.org/ns/data-models#externalSecurity", + "extraSpotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/extraSpotNumber", + "facilities": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/facilities", + "failed": "https://uri.fiware.org/ns/data-models#failed", + "fair": "https://uri.fiware.org/ns/data-models#fair", + "fairPermit": "https://uri.fiware.org/ns/data-models#fairPermit", + "fairground": "https://uri.fiware.org/ns/data-models#fairground", + "fallenPatient": "https://uri.fiware.org/ns/data-models#fallenPatient", + "falling": "https://uri.fiware.org/ns/data-models#falling", + "farm": "https://uri.fiware.org/ns/data-models#farm", + "farm_auxiliary": "https://uri.fiware.org/ns/data-models#farm_auxiliary", + "faxMachineOrService": "https://uri.fiware.org/ns/data-models#faxMachineOrService", + "façade": "https://uri.fiware.org/ns/data-models#façade", + "feature": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/feature", + "featuredArtist": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/featuredArtist", + "features": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/features", + "fedWith": { + "@id": "https://uri.fiware.org/ns/data-models#fedWith", + "@type": "@id" + }, + "feeCharged": "https://uri.fiware.org/ns/data-models#feeCharged", + "feelLikesTemperature": "https://uri.fiware.org/ns/data-models#feelLikesTemperature", + "female": "https://uri.fiware.org/ns/data-models#female", + "femaleAdult": "https://uri.fiware.org/ns/data-models#femaleAdult", + "femaleYoung": "https://uri.fiware.org/ns/data-models#femaleYoung", + "fenced": "https://uri.fiware.org/ns/data-models#fenced", + "fencedOff": "https://uri.fiware.org/ns/data-models#fencedOff", + "fences": "https://uri.fiware.org/ns/data-models#fences", + "ferryTerminal": "https://uri.fiware.org/ns/data-models#ferryTerminal", + "fertilisation": "https://uri.fiware.org/ns/data-models#fertilisation", + "fertiliser": "https://uri.fiware.org/ns/data-models#fertiliser", + "field": "https://uri.fiware.org/ns/data-models#field", + "fillingLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/fillingLevel", + "financial": "https://uri.fiware.org/ns/data-models#financial", + "fineArts": "https://uri.fiware.org/ns/data-models#fineArts", + "finished": "https://uri.fiware.org/ns/data-models#finished", + "fireBrigade": "https://uri.fiware.org/ns/data-models#fireBrigade", + "fireExtinguisher": "https://uri.fiware.org/ns/data-models#fireExtinguisher", + "fireHose": "https://uri.fiware.org/ns/data-models#fireHose", + "fireHydrant": "https://uri.fiware.org/ns/data-models#fireHydrant", + "fireRisk": "https://uri.fiware.org/ns/data-models#fireRisk", + "firmwareVersion": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/firmwareVersion", + "firstAidEquipment": "https://uri.fiware.org/ns/data-models#firstAidEquipment", + "firstAvailableFloor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/firstAvailableFloor", + "firstIntervalPrice": "https://uri.fiware.org/ns/data-models#firstIntervalPrice", + "fishMarket": "https://uri.fiware.org/ns/data-models#fishMarket", + "fixed": "https://uri.fiware.org/ns/data-models#fixed", + "flashingBeacon": "https://uri.fiware.org/ns/data-models#flashingBeacon", + "flat": "https://uri.fiware.org/ns/data-models#flat", + "fleetVehicleId": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/fleetVehicleId", + "flood": "https://uri.fiware.org/ns/data-models#flood", + "floodLight": "https://uri.fiware.org/ns/data-models#floodLight", + "floodRisk": "https://uri.fiware.org/ns/data-models#floodRisk", + "floorsAboveGround": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/floorsAboveGround", + "floorsBelowGround": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/floorsBelowGround", + "flow": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/flow", + "fog": "https://uri.fiware.org/ns/data-models#fog", + "forBabies": "https://uri.fiware.org/ns/data-models#forBabies", + "forCustomers": "https://uri.fiware.org/ns/data-models#forCustomers", + "forDisabled": "https://uri.fiware.org/ns/data-models#forDisabled", + "forElectricalCharging": "https://uri.fiware.org/ns/data-models#forElectricalCharging", + "forEmployees": "https://uri.fiware.org/ns/data-models#forEmployees", + "forMembers": "https://uri.fiware.org/ns/data-models#forMembers", + "forResidents": "https://uri.fiware.org/ns/data-models#forResidents", + "forStudents": "https://uri.fiware.org/ns/data-models#forStudents", + "forVisitors": "https://uri.fiware.org/ns/data-models#forVisitors", + "forestFire": "https://uri.fiware.org/ns/data-models#forestFire", + "format": "https://uri.etsi.org/ngsi-ld/format", + "fortifiedTemple": "https://uri.fiware.org/ns/data-models#fortifiedTemple", + "fortress": "https://uri.fiware.org/ns/data-models#fortress", + "forward": "https://uri.fiware.org/ns/data-models#forward", + "free": "https://uri.fiware.org/ns/data-models#free", + "freeAccess": "https://uri.fiware.org/ns/data-models#freeAccess", + "freeSlotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/freeSlotNumber", + "french": "https://uri.fiware.org/ns/data-models#french", + "frequencies": "https://uri.fiware.org/ns/data-models#frequencies", + "frequency": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/frequency", + "freshWater": "https://uri.fiware.org/ns/data-models#freshWater", + "friday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/friday", + "fuelConsumption": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/fuelConsumption", + "fuelType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/fuelType", + "full": "https://uri.fiware.org/ns/data-models#full", + "fullAtEntrance": "https://uri.fiware.org/ns/data-models#fullAtEntrance", + "function": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/function", + "fungus": "https://uri.fiware.org/ns/data-models#fungus", + "garage": "https://uri.fiware.org/ns/data-models#garage", + "garageBoxes": "https://uri.fiware.org/ns/data-models#garageBoxes", + "garages": "https://uri.fiware.org/ns/data-models#garages", + "garbageCollection": "https://uri.fiware.org/ns/data-models#garbageCollection", + "garbage_shed": "https://uri.fiware.org/ns/data-models#garbage_shed", + "garden": "https://uri.fiware.org/ns/data-models#garden", + "gasComsumption": "https://uri.fiware.org/ns/data-models#gasComsumption", + "gasoline": "https://uri.fiware.org/ns/data-models#gasoline", + "gate": "https://uri.fiware.org/ns/data-models#gate", + "gateAccess": "https://uri.fiware.org/ns/data-models#gateAccess", + "generationSources": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/generationSources", + "geoQ": "https://uri.etsi.org/ngsi-ld/geoQ", + "geometry": "https://uri.etsi.org/ngsi-ld/geometry", + "geoproperty": "https://uri.etsi.org/ngsi-ld/geoproperty", + "georel": "https://uri.etsi.org/ngsi-ld/georel", + "geothermalPowerGenerated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/geothermalPowerGenerated", + "glass": "https://uri.fiware.org/ns/data-models#glass", + "goat": "https://uri.fiware.org/ns/data-models#goat", + "good": "https://uri.fiware.org/ns/data-models#good", + "goodsSelling": "https://uri.fiware.org/ns/data-models#goodsSelling", + "governmentPermit": "https://uri.fiware.org/ns/data-models#governmentPermit", + "gps": "https://uri.fiware.org/ns/data-models#gps", + "grandstand": "https://uri.fiware.org/ns/data-models#grandstand", + "grave": "https://uri.fiware.org/ns/data-models#grave", + "graveyard": "https://uri.fiware.org/ns/data-models#graveyard", + "grazingBaby": "https://uri.fiware.org/ns/data-models#grazingBaby", + "greenhouse": "https://uri.fiware.org/ns/data-models#greenhouse", + "ground": "https://uri.fiware.org/ns/data-models#ground", + "group": "https://uri.fiware.org/ns/data-models#group", + "growing": "https://uri.fiware.org/ns/data-models#growing", + "guard24hours": "https://uri.fiware.org/ns/data-models#guard24hours", + "guarded": "https://uri.fiware.org/ns/data-models#guarded", + "guidedTour": "https://uri.fiware.org/ns/data-models#guidedTour", + "hangar": "https://uri.fiware.org/ns/data-models#hangar", + "hardwareVersion": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hardwareVersion", + "harvestCommodity": "https://uri.fiware.org/ns/data-models#harvestCommodity", + "harvestingInterval": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/harvestingInterval", + "hasAccessPoint": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAccessPoint", + "hasAgriCrop": { + "@id": "https://uri.fiware.org/ns/data-models#hasAgriCrop", + "@type": "@id" + }, + "hasAgriFertiliser": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriFertiliser", + "hasAgriParcel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriParcel", + "hasAgriParcelChildren": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriParcelChildren", + "hasAgriParcelParent": { + "@id": "https://uri.fiware.org/ns/data-models#hasAgriParcelParent", + "@type": "@id" + }, + "hasAgriPest": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriPest", + "hasAgriProductType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriProductType", + "hasAgriProductTypeChildren": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriProductTypeChildren", + "hasAgriProductTypeParent": { + "@id": "https://uri.fiware.org/ns/data-models#hasAgriProductTypeParent", + "@type": "@id" + }, + "hasAgriSoil": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasAgriSoil", + "hasBuilding": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasBuilding", + "hasDestination": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasDestination", + "hasDevice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasDevice", + "hasDevices": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasDevices", + "hasOperator": { + "@id": "https://uri.fiware.org/ns/data-models#hasOperator", + "@type": "@id" + }, + "hasOrigin": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasOrigin", + "hasParentStation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasParentStation", + "hasProvider": { + "@id": "https://uri.fiware.org/ns/data-models#hasProvider", + "@type": "@id" + }, + "hasRoute": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasRoute", + "hasService": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasService", + "hasShape": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasShape", + "hasStop": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasStop", + "hasTrip": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasTrip", + "hasWaterQualityObserved": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hasWaterQualityObserved", + "hasWeatherObserved": { + "@id": "https://uri.fiware.org/ns/data-models#hasWeatherObserved", + "@type": "@id" + }, + "hazardOnRoad": "https://uri.fiware.org/ns/data-models#hazardOnRoad", + "hazardous": "https://uri.fiware.org/ns/data-models#hazardous", + "headCurve": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/headCurve", + "headPattern": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/headPattern", + "headSign": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/headSign", + "heading": "https://uri.fiware.org/ns/data-models#heading", + "headsign": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/headsign", + "headwaySeconds": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/headwaySeconds", + "health": "https://uri.fiware.org/ns/data-models#health", + "healthCondition": { + "@id": "https://uri.fiware.org/ns/data-models#healthCondition", + "@type": "@vocab" + }, + "healthy": "https://uri.fiware.org/ns/data-models#healthy", + "heartAttack": "https://uri.fiware.org/ns/data-models#heartAttack", + "heatWave": "https://uri.fiware.org/ns/data-models#heatWave", + "hedge": "https://uri.fiware.org/ns/data-models#hedge", + "height": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/height", + "herb_garden": "https://uri.fiware.org/ns/data-models#herb_garden", + "hermitage": "https://uri.fiware.org/ns/data-models#hermitage", + "high": "https://uri.fiware.org/ns/data-models#high", + "highTemperature": "https://uri.fiware.org/ns/data-models#highTemperature", + "highest": "https://uri.fiware.org/ns/data-models#highest", + "highestFloor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/highestFloor", + "historicalPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/historicalPeriod", + "history": "https://uri.fiware.org/ns/data-models#history", + "horse": "https://uri.fiware.org/ns/data-models#horse", + "hospital": "https://uri.fiware.org/ns/data-models#hospital", + "hostelry": "https://uri.fiware.org/ns/data-models#hostelry", + "hotel": "https://uri.fiware.org/ns/data-models#hotel", + "hourly": "https://uri.fiware.org/ns/data-models#hourly", + "house": "https://uri.fiware.org/ns/data-models#house", + "houseBuilding": "https://uri.fiware.org/ns/data-models#houseBuilding", + "houseboat": "https://uri.fiware.org/ns/data-models#houseboat", + "household": "https://uri.fiware.org/ns/data-models#household", + "humidity": "https://uri.fiware.org/ns/data-models#humidity", + "hurricane": "https://uri.fiware.org/ns/data-models#hurricane", + "hut": "https://uri.fiware.org/ns/data-models#hut", + "hybrid_electric_diesel": "https://uri.fiware.org/ns/data-models#hybrid_electric_diesel", + "hybrid_electric_petrol": "https://uri.fiware.org/ns/data-models#hybrid_electric_petrol", + "hydroPowerGenerated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/hydroPowerGenerated", + "hydrogen": "https://uri.fiware.org/ns/data-models#hydrogen", + "ice": "https://uri.fiware.org/ns/data-models#ice", + "iceFreeScaffold": "https://uri.fiware.org/ns/data-models#iceFreeScaffold", + "id": "@id", + "idPattern": "https://uri.etsi.org/ngsi-ld/idPattern", + "illuminance": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/illuminance", + "illuminanceLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/illuminanceLevel", + "image": "https://schema.org/image", + "inCalf": "https://uri.fiware.org/ns/data-models#inCalf", + "inHeat": "https://uri.fiware.org/ns/data-models#inHeat", + "inTreatment": "https://uri.fiware.org/ns/data-models#inTreatment", + "inactive": "https://uri.fiware.org/ns/data-models#inactive", + "inbound": "https://uri.fiware.org/ns/data-models#inbound", + "individual": "https://uri.fiware.org/ns/data-models#individual", + "individualControl": "https://uri.fiware.org/ns/data-models#individualControl", + "industrial": "https://uri.fiware.org/ns/data-models#industrial", + "industrialBuilding": "https://uri.fiware.org/ns/data-models#industrialBuilding", + "information": "https://uri.etsi.org/ngsi-ld/information", + "informationPoint": "https://uri.fiware.org/ns/data-models#informationPoint", + "informational": "https://uri.fiware.org/ns/data-models#informational", + "infotainment": "https://uri.fiware.org/ns/data-models#infotainment", + "initLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/initLevel", + "initialQuality": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/initialQuality", + "initialStatus": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/initialStatus", + "injuredBiker": "https://uri.fiware.org/ns/data-models#injuredBiker", + "inorganic": "https://uri.fiware.org/ns/data-models#inorganic", + "input": "https://uri.fiware.org/ns/data-models#input", + "insect": "https://uri.fiware.org/ns/data-models#insect", + "insertHoles": "https://uri.fiware.org/ns/data-models#insertHoles", + "insertHolesNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/insertHolesNumber", + "inspection": "https://uri.fiware.org/ns/data-models#inspection", + "instanceId": { + "@id": "https://uri.etsi.org/ngsi-ld/instanceId", + "@type": "@id" + }, + "intensity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/intensity", + "intensityR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/intensityR", + "intensityS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/intensityS", + "intensityT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/intensityT", + "internetConnection": "https://uri.fiware.org/ns/data-models#internetConnection", + "internetWireless": "https://uri.fiware.org/ns/data-models#internetWireless", + "ipAddress": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/ipAddress", + "irrigation": "https://uri.fiware.org/ns/data-models#irrigation", + "irrigationRecord": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/irrigationRecord", + "isActive": "https://uri.etsi.org/ngsi-ld/isActive", + "isleId": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/isleId", + "isolated": "https://uri.fiware.org/ns/data-models#isolated", + "issue": "https://uri.fiware.org/ns/data-models#issue", + "japanese": "https://uri.fiware.org/ns/data-models#japanese", + "justBorn": "https://uri.fiware.org/ns/data-models#justBorn", + "kindergarten": "https://uri.fiware.org/ns/data-models#kindergarten", + "kiosk": "https://uri.fiware.org/ns/data-models#kiosk", + "kissAndRide": "https://uri.fiware.org/ns/data-models#kissAndRide", + "kitchen": "https://uri.fiware.org/ns/data-models#kitchen", + "kpiValue": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/kpiValue", + "lactatingBaby": "https://uri.fiware.org/ns/data-models#lactatingBaby", + "lagging": "https://uri.fiware.org/ns/data-models#lagging", + "lampBrandName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lampBrandName", + "lampManufacturerName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lampManufacturerName", + "lampModelName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lampModelName", + "lampTechnology": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lampTechnology", + "lampWeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lampWeight", + "lamppost": "https://uri.fiware.org/ns/data-models#lamppost", + "landLocation": "https://uri.fiware.org/ns/data-models#landLocation", + "laneDirection": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/laneDirection", + "laneId": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/laneId", + "laneUsage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/laneUsage", + "language": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/language", + "lanternBrandName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lanternBrandName", + "lanternManufacturerName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lanternManufacturerName", + "lanternModelName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lanternModelName", + "lanternWeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lanternWeight", + "lastFailure": { + "@id": "https://uri.etsi.org/ngsi-ld/lastFailure", + "@type": "DateTime" + }, + "lastMeterReading": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lastMeterReading", + "lastNotification": { + "@id": "https://uri.etsi.org/ngsi-ld/lastNotification", + "@type": "DateTime" + }, + "lastPlantedAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lastPlantedAt", + "lastSuccess": { + "@id": "https://uri.etsi.org/ngsi-ld/lastSuccess", + "@type": "DateTime" + }, + "lastupdatedAt": { + "@id": "https://uri.fiware.org/ns/data-models#lastupdatedAt", + "@type": "https://uri.etsi.org/ngsi-ld/DateTime" + }, + "laternHeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/laternHeight", + "lawnArea": "https://uri.fiware.org/ns/data-models#lawnArea", + "layout": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/layout", + "leading": "https://uri.fiware.org/ns/data-models#leading", + "leafRelativeHumidity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/leafRelativeHumidity", + "leafTemperature": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/leafTemperature", + "leafWetness": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/leafWetness", + "legalId": "https://uri.fiware.org/ns/data-models#legalId", + "length": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/length", + "levelControl": "https://uri.fiware.org/ns/data-models#levelControl", + "lid": "https://uri.fiware.org/ns/data-models#lid", + "lidOpen": "https://uri.fiware.org/ns/data-models#lidOpen", + "lifeGuard": "https://uri.fiware.org/ns/data-models#lifeGuard", + "liftshare": "https://uri.fiware.org/ns/data-models#liftshare", + "light": "https://uri.fiware.org/ns/data-models#light", + "lightTower": "https://uri.fiware.org/ns/data-models#lightTower", + "lighting": "https://uri.fiware.org/ns/data-models#lighting", + "link": "https://uri.fiware.org/ns/data-models#link", + "literature": "https://uri.fiware.org/ns/data-models#literature", + "litterBins": "https://uri.fiware.org/ns/data-models#litterBins", + "loadingBay": "https://uri.fiware.org/ns/data-models#loadingBay", + "locatedAt": { + "@id": "https://uri.fiware.org/ns/data-models#locatedAt", + "@type": "@id" + }, + "location": "http://uri.etsi.org/ngsi-ld/location", + "locationCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/locationCategory", + "lockable": "https://uri.fiware.org/ns/data-models#lockable", + "logo": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/logo", + "longTerm": "https://uri.fiware.org/ns/data-models#longTerm", + "lorry": "https://uri.fiware.org/ns/data-models#lorry", + "low": "https://uri.fiware.org/ns/data-models#low", + "lowTemperature": "https://uri.fiware.org/ns/data-models#lowTemperature", + "lowest": "https://uri.fiware.org/ns/data-models#lowest", + "lowestFloor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/lowestFloor", + "lpg": "https://uri.fiware.org/ns/data-models#lpg", + "luggageLocker": "https://uri.fiware.org/ns/data-models#luggageLocker", + "luminousFlux": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/luminousFlux", + "macAddress": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/macAddress", + "madeOf": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/madeOf", + "madeOfCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/madeOfCode", + "maintenance": "https://uri.fiware.org/ns/data-models#maintenance", + "male": "https://uri.fiware.org/ns/data-models#male", + "maleAdult": "https://uri.fiware.org/ns/data-models#maleAdult", + "maleYoung": "https://uri.fiware.org/ns/data-models#maleYoung", + "managementInterval": "https://uri.etsi.org/ngsi-ld/managementInterval", + "mandatory": "https://uri.fiware.org/ns/data-models#mandatory", + "mansion": "https://uri.fiware.org/ns/data-models#mansion", + "manual": "https://uri.fiware.org/ns/data-models#manual", + "manufacturerName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/manufacturerName", + "maritime": "https://uri.fiware.org/ns/data-models#maritime", + "market": "https://uri.fiware.org/ns/data-models#market", + "masia": "https://uri.fiware.org/ns/data-models#masia", + "masiaFortificada": "https://uri.fiware.org/ns/data-models#masiaFortificada", + "maturing": "https://uri.fiware.org/ns/data-models#maturing", + "maxBiomassMeasure": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxBiomassMeasure", + "maxEolicPowerMeasure": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxEolicPowerMeasure", + "maxHydroPowerMeasure": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxHydroPowerMeasure", + "maxLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxLevel", + "maxPowerConsumption": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxPowerConsumption", + "maxSolarPowerMeasure": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maxSolarPowerMeasure", + "maximum": "https://uri.fiware.org/ns/data-models#maximum", + "maximumAllowedDuration": "https://uri.fiware.org/ns/data-models#maximumAllowedDuration", + "maximumAllowedHeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumAllowedHeight", + "maximumAllowedSpeed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumAllowedSpeed", + "maximumAllowedWeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumAllowedWeight", + "maximumAllowedWidth": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumAllowedWidth", + "maximumLoad": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumLoad", + "maximumParkingDuration": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumParkingDuration", + "maximumPowerAvailable": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/maximumPowerAvailable", + "mcc": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/mcc", + "measurand": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/measurand", + "measuredArea": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/measuredArea", + "measuresPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/measuresPeriod", + "measuresPeriodUnit": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/measuresPeriodUnit", + "medicineAndPharmacy": "https://uri.fiware.org/ns/data-models#medicineAndPharmacy", + "medium": "https://uri.fiware.org/ns/data-models#medium", + "mediumTerm": "https://uri.fiware.org/ns/data-models#mediumTerm", + "menhir": "https://uri.fiware.org/ns/data-models#menhir", + "metal": "https://uri.fiware.org/ns/data-models#metal", + "meterReadingPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/meterReadingPeriod", + "metering": "https://uri.fiware.org/ns/data-models#metering", + "methaneConcentration": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/methaneConcentration", + "microbe": "https://uri.fiware.org/ns/data-models#microbe", + "mileageFromOdometer": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/mileageFromOdometer", + "military": "https://uri.fiware.org/ns/data-models#military", + "militaryBuilding": "https://uri.fiware.org/ns/data-models#militaryBuilding", + "minLevel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minLevel", + "minPowerConsumption": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minPowerConsumption", + "minVolume": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minVolume", + "minaret": "https://uri.fiware.org/ns/data-models#minaret", + "minibus": "https://uri.fiware.org/ns/data-models#minibus", + "minimum": "https://uri.fiware.org/ns/data-models#minimum", + "minimumAllowedSpeed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minimumAllowedSpeed", + "minimumTransferTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minimumTransferTime", + "mining": "https://uri.fiware.org/ns/data-models#mining", + "minorLoss": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/minorLoss", + "mite": "https://uri.fiware.org/ns/data-models#mite", + "mixingFraction": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/mixingFraction", + "mixingModel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/mixingModel", + "mnc": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/mnc", + "modelBased": "https://uri.fiware.org/ns/data-models#modelBased", + "modelName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/modelName", + "moderate": "https://uri.fiware.org/ns/data-models#moderate", + "modernArt": "https://uri.fiware.org/ns/data-models#modernArt", + "modifiedAt": { + "@id": "https://uri.etsi.org/ngsi-ld/modifiedAt", + "@type": "DateTime" + }, + "monastary": "https://uri.fiware.org/ns/data-models#monastary", + "monastery": "https://uri.fiware.org/ns/data-models#monastery", + "monday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/monday", + "monolith": "https://uri.fiware.org/ns/data-models#monolith", + "monthly": "https://uri.fiware.org/ns/data-models#monthly", + "monthlyPayment": "https://uri.fiware.org/ns/data-models#monthlyPayment", + "moped": "https://uri.fiware.org/ns/data-models#moped", + "mosque": "https://uri.fiware.org/ns/data-models#mosque", + "motion": "https://uri.fiware.org/ns/data-models#motion", + "motorcycle": "https://uri.fiware.org/ns/data-models#motorcycle", + "motorcycleWithSideCar": "https://uri.fiware.org/ns/data-models#motorcycleWithSideCar", + "motorscooter": "https://uri.fiware.org/ns/data-models#motorscooter", + "motorway": "https://uri.fiware.org/ns/data-models#motorway", + "moved": "https://uri.fiware.org/ns/data-models#moved", + "multiDisciplinar": "https://uri.fiware.org/ns/data-models#multiDisciplinar", + "multiLevel": "https://uri.fiware.org/ns/data-models#multiLevel", + "multiStorey": "https://uri.fiware.org/ns/data-models#multiStorey", + "multipliers": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/multipliers", + "municipal": "https://uri.fiware.org/ns/data-models#municipal", + "municipalServices": "https://uri.fiware.org/ns/data-models#municipalServices", + "museumHouse": "https://uri.fiware.org/ns/data-models#museumHouse", + "museumType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/museumType", + "music": "https://uri.fiware.org/ns/data-models#music", + "name": "https://uri.etsi.org/ngsi-ld/name", + "naturalDisaster": "https://uri.fiware.org/ns/data-models#naturalDisaster", + "naturalScience": "https://uri.fiware.org/ns/data-models#naturalScience", + "necropolis": "https://uri.fiware.org/ns/data-models#necropolis", + "nematodes": "https://uri.fiware.org/ns/data-models#nematodes", + "nested": "https://uri.fiware.org/ns/data-models#nested", + "network": "https://uri.fiware.org/ns/data-models#network", + "nextActuationDeadline": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/nextActuationDeadline", + "nextCleaningDeadline": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/nextCleaningDeadline", + "nextWateringDeadline": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/nextWateringDeadline", + "ngsi-ld": "https://uri.etsi.org/ngsi-ld/", + "ni": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/ni", + "night-LOW": "https://uri.fiware.org/ns/data-models#night-LOW", + "night-OFF": "https://uri.fiware.org/ns/data-models#night-OFF", + "night-ON": "https://uri.fiware.org/ns/data-models#night-ON", + "no": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/no", + "no2": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/no2", + "noPermitNeeded": "https://uri.fiware.org/ns/data-models#noPermitNeeded", + "noStatus": "https://uri.fiware.org/ns/data-models#noStatus", + "nobleHouse": "https://uri.fiware.org/ns/data-models#nobleHouse", + "noiseLevel": "https://uri.fiware.org/ns/data-models#noiseLevel", + "nominalDiameter": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/nominalDiameter", + "nonTracked": "https://uri.fiware.org/ns/data-models#nonTracked", + "none": "https://uri.fiware.org/ns/data-models#none", + "notAvailable": "https://uri.fiware.org/ns/data-models#notAvailable", + "notification": "https://uri.etsi.org/ngsi-ld/notification", + "notifiedAt": { + "@id": "https://uri.etsi.org/ngsi-ld/notifiedAt", + "@type": "DateTime" + }, + "noxiousWeed": "https://uri.fiware.org/ns/data-models#noxiousWeed", + "numismatic": "https://uri.fiware.org/ns/data-models#numismatic", + "o3": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/o3", + "object": { + "@id": "https://uri.etsi.org/ngsi-ld/hasObject", + "@type": "@id" + }, + "objectArea": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/objectArea", + "objectHeightAverage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/objectHeightAverage", + "objectHeightMax": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/objectHeightMax", + "objectVolume": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/objectVolume", + "objects": { + "@id": "https://uri.etsi.org/ngsi-ld/hasObjects", + "@type": "@id", + "@container": "@list" + }, + "observationInterval": "https://uri.etsi.org/ngsi-ld/observationInterval", + "observationSpace": "https://uri.etsi.org/ngsi-ld/observationSpace", + "observedAt": { + "@id": "https://uri.etsi.org/ngsi-ld/observedAt", + "@type": "DateTime" + }, + "occupancy": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/occupancy", + "occupancyDetectionType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/occupancyDetectionType", + "occupationRate": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/occupationRate", + "occupied": "https://uri.fiware.org/ns/data-models#occupied", + "occupier": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/occupier", + "off": "https://uri.fiware.org/ns/data-models#off", + "office": "https://uri.fiware.org/ns/data-models#office", + "officeBuilding": "https://uri.fiware.org/ns/data-models#officeBuilding", + "offstreet": "https://uri.fiware.org/ns/data-models#offstreet", + "oil": "https://uri.fiware.org/ns/data-models#oil", + "ok": "https://uri.fiware.org/ns/data-models#ok", + "on": "https://uri.fiware.org/ns/data-models#on", + "onDemand": "https://uri.fiware.org/ns/data-models#onDemand", + "onFoot": "https://uri.fiware.org/ns/data-models#onFoot", + "onOff": "https://uri.fiware.org/ns/data-models#onOff", + "onRoute": "https://uri.fiware.org/ns/data-models#onRoute", + "oneway": "https://uri.fiware.org/ns/data-models#oneway", + "ongoing": "https://uri.fiware.org/ns/data-models#ongoing", + "onlyResidents": "https://uri.fiware.org/ns/data-models#onlyResidents", + "onlyWithPermit": "https://uri.fiware.org/ns/data-models#onlyWithPermit", + "onstreet": "https://uri.fiware.org/ns/data-models#onstreet", + "open": "https://uri.fiware.org/ns/data-models#open", + "openClose": "https://uri.fiware.org/ns/data-models#openClose", + "openSpace": "https://uri.fiware.org/ns/data-models#openSpace", + "openingHours": "https://schema.org/openingHours", + "openingHoursSpecification": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/openingHoursSpecification", + "openingTimesInForce": "https://uri.fiware.org/ns/data-models#openingTimesInForce", + "operatedBy": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/operatedBy", + "operationSequence": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/operationSequence", + "operationSpace": "https://uri.etsi.org/ngsi-ld/operationSpace", + "operationType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/operationType", + "operator": "https://uri.fiware.org/ns/data-models#operator", + "optional": "https://uri.fiware.org/ns/data-models#optional", + "organic": "https://uri.fiware.org/ns/data-models#organic", + "organization": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/organization", + "ornamentalLantern": "https://uri.fiware.org/ns/data-models#ornamentalLantern", + "orp": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/orp", + "osVersion": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/osVersion", + "other": "https://uri.fiware.org/ns/data-models#other", + "outOfService": "https://uri.fiware.org/ns/data-models#outOfService", + "outOfServiceSlotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/outOfServiceSlotNumber", + "outbound": "https://uri.fiware.org/ns/data-models#outbound", + "output": "https://uri.fiware.org/ns/data-models#output", + "overnightParking": "https://uri.fiware.org/ns/data-models#overnightParking", + "overspeed": "https://uri.fiware.org/ns/data-models#overspeed", + "ownedBy": { + "@id": "https://uri.fiware.org/ns/data-models#ownedBy", + "@type": "@id" + }, + "owner": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/owner", + "pH": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pH", + "page": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/page", + "painting": "https://uri.fiware.org/ns/data-models#painting", + "palace": "https://uri.fiware.org/ns/data-models#palace", + "paleonthology": "https://uri.fiware.org/ns/data-models#paleonthology", + "pantheon": "https://uri.fiware.org/ns/data-models#pantheon", + "paper": "https://uri.fiware.org/ns/data-models#paper", + "parallelParking": "https://uri.fiware.org/ns/data-models#parallelParking", + "park": "https://uri.fiware.org/ns/data-models#park", + "parkAndCycle": "https://uri.fiware.org/ns/data-models#parkAndCycle", + "parkAndRide": "https://uri.fiware.org/ns/data-models#parkAndRide", + "parkAndWalk": "https://uri.fiware.org/ns/data-models#parkAndWalk", + "parked": "https://uri.fiware.org/ns/data-models#parked", + "parking": "https://uri.fiware.org/ns/data-models#parking", + "parkingGarage": "https://uri.fiware.org/ns/data-models#parkingGarage", + "parkingLot": "https://uri.fiware.org/ns/data-models#parkingLot", + "parkingMode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/parkingMode", + "parksAndGardens": "https://uri.fiware.org/ns/data-models#parksAndGardens", + "partly": "https://uri.fiware.org/ns/data-models#partly", + "patrolled": "https://uri.fiware.org/ns/data-models#patrolled", + "pavilion": "https://uri.fiware.org/ns/data-models#pavilion", + "payDesk": "https://uri.fiware.org/ns/data-models#payDesk", + "paymentAccepted": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/paymentAccepted", + "paymentMachine": "https://uri.fiware.org/ns/data-models#paymentMachine", + "pazo": "https://uri.fiware.org/ns/data-models#pazo", + "pb": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pb", + "pedestrianPath": "https://uri.fiware.org/ns/data-models#pedestrianPath", + "peopleCount": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/peopleCount", + "perpendicularParking": "https://uri.fiware.org/ns/data-models#perpendicularParking", + "pesticide": "https://uri.fiware.org/ns/data-models#pesticide", + "petrol": "https://uri.fiware.org/ns/data-models#petrol", + "petrol(leaded)": "https://uri.fiware.org/ns/data-models#petrol(leaded)", + "petrol(unleaded)": "https://uri.fiware.org/ns/data-models#petrol(unleaded)", + "phaseToPhaseVoltage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/phaseToPhaseVoltage", + "phaseVoltage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/phaseVoltage", + "phenologicalCondition": { + "@id": "https://uri.fiware.org/ns/data-models#phenologicalCondition", + "@type": "@vocab" + }, + "phone": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/phone", + "pickupType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pickupType", + "pig": "https://uri.fiware.org/ns/data-models#pig", + "planned": "https://uri.fiware.org/ns/data-models#planned", + "plannedEndAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/plannedEndAt", + "plannedStartAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/plannedStartAt", + "plantingFrom": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/plantingFrom", + "plastic": "https://uri.fiware.org/ns/data-models#plastic", + "playground": "https://uri.fiware.org/ns/data-models#playground", + "pm10": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pm10", + "pm25": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pm25", + "police": "https://uri.fiware.org/ns/data-models#police", + "pollenConcentration": "https://uri.fiware.org/ns/data-models#pollenConcentration", + "polularArchitecture": "https://uri.fiware.org/ns/data-models#polularArchitecture", + "polygon": "https://uri.fiware.org/ns/data-models#polygon", + "popularArtsAndTraditions": "https://uri.fiware.org/ns/data-models#popularArtsAndTraditions", + "portable": "https://uri.fiware.org/ns/data-models#portable", + "postTop": "https://uri.fiware.org/ns/data-models#postTop", + "pothole": "https://uri.fiware.org/ns/data-models#pothole", + "power": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/power", + "powerConsumption": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerConsumption", + "powerFactor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerFactor", + "powerFactorR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerFactorR", + "powerFactorS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerFactorS", + "powerFactorT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerFactorT", + "powerState": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/powerState", + "practical": "https://uri.fiware.org/ns/data-models#practical", + "precipitation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/precipitation", + "precipitationProbability": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/precipitationProbability", + "prehistoric": "https://uri.fiware.org/ns/data-models#prehistoric", + "prehistoricCave": "https://uri.fiware.org/ns/data-models#prehistoricCave", + "prehistoricPlace": "https://uri.fiware.org/ns/data-models#prehistoricPlace", + "pressure": "https://uri.fiware.org/ns/data-models#pressure", + "pressureTendency": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pressureTendency", + "previousLocation": "https://uri.fiware.org/ns/data-models#previousLocation", + "priceCurrency": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/priceCurrency", + "priceRatePerMinute": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/priceRatePerMinute", + "primary": "https://uri.fiware.org/ns/data-models#primary", + "private": "https://uri.fiware.org/ns/data-models#private", + "privateVehicle": "https://uri.fiware.org/ns/data-models#privateVehicle", + "proCathedral": "https://uri.fiware.org/ns/data-models#proCathedral", + "process": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/process", + "product": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/product", + "promenade": "https://uri.fiware.org/ns/data-models#promenade", + "properties": { + "@id": "https://uri.etsi.org/ngsi-ld/properties", + "@type": "@vocab" + }, + "provider": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/provider", + "proximitySensor": "https://uri.fiware.org/ns/data-models#proximitySensor", + "public": "https://uri.fiware.org/ns/data-models#public", + "publicBuilding": "https://uri.fiware.org/ns/data-models#publicBuilding", + "publicPhone": "https://uri.fiware.org/ns/data-models#publicPhone", + "publicPrivate": "https://uri.fiware.org/ns/data-models#publicPrivate", + "publicTransport": "https://uri.fiware.org/ns/data-models#publicTransport", + "publicTransportStation": "https://uri.fiware.org/ns/data-models#publicTransportStation", + "pumpPattern": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/pumpPattern", + "purchaseDate": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/purchaseDate", + "pyramid": "https://uri.fiware.org/ns/data-models#pyramid", + "q": "https://uri.etsi.org/ngsi-ld/q", + "qualitative": "https://uri.fiware.org/ns/data-models#qualitative", + "quantitative": "https://uri.fiware.org/ns/data-models#quantitative", + "quantity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/quantity", + "quarterly": "https://uri.fiware.org/ns/data-models#quarterly", + "railway": "https://uri.fiware.org/ns/data-models#railway", + "rainfall": "https://uri.fiware.org/ns/data-models#rainfall", + "rainwater_capture": "https://uri.fiware.org/ns/data-models#rainwater_capture", + "raising": "https://uri.fiware.org/ns/data-models#raising", + "ramp": "https://uri.fiware.org/ns/data-models#ramp", + "reactiveEnergyConsumed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactiveEnergyConsumed", + "reactiveEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactiveEnergyExport", + "reactiveEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactiveEnergyImport", + "reactivePower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactivePower", + "reactivePowerR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactivePowerR", + "reactivePowerS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactivePowerS", + "reactivePowerT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reactivePowerT", + "readyForHarvesting": "https://uri.fiware.org/ns/data-models#readyForHarvesting", + "reason": "https://uri.etsi.org/ngsi-ld/reason", + "rechargeTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/rechargeTime", + "recommendedLoad": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/recommendedLoad", + "rectangular": "https://uri.fiware.org/ns/data-models#rectangular", + "redistribution": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/redistribution", + "refActivity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refActivity", + "refAgent": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refAgent", + "refBattery": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refBattery", + "refBuilding": { + "@id": "https://uri.fiware.org/ns/data-models#refBuilding", + "@type": "@id" + }, + "refDevice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refDevice", + "refDeviceModel": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refDeviceModel", + "refGarden": { + "@id": "https://uri.fiware.org/ns/data-models#refGarden", + "@type": "@id" + }, + "refGreenspace": { + "@id": "https://uri.fiware.org/ns/data-models#refGreenspace", + "@type": "@id" + }, + "refGtfsTransitFeedFile": "https://uri.fiware.org/ns/data-models#refGtfsTransitFeedFile", + "refMap": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refMap", + "refObject": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refObject", + "refOperator": { + "@id": "https://uri.fiware.org/ns/data-models#refOperator", + "@type": "@id" + }, + "refParkingAccess": { + "@id": "https://uri.fiware.org/ns/data-models#refParkingAccess", + "@type": "@id" + }, + "refParkingGroup": { + "@id": "https://uri.fiware.org/ns/data-models#refParkingGroup", + "@type": "@id" + }, + "refParkingSite": { + "@id": "https://uri.fiware.org/ns/data-models#refParkingSite", + "@type": "@id" + }, + "refParkingSpot": { + "@id": "https://uri.fiware.org/ns/data-models#refParkingSpot", + "@type": "@id" + }, + "refPeopleCountDevice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refPeopleCountDevice", + "refPointOfInterest": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refPointOfInterest", + "refPublicTransportRoute": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refPublicTransportRoute", + "refRecord": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refRecord", + "refRelatedBuildingOperation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refRelatedBuildingOperation", + "refRelatedDeviceOperation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refRelatedDeviceOperation", + "refRelatedEntity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refRelatedEntity", + "refRoad": { + "@id": "https://uri.fiware.org/ns/data-models#refRoad", + "@type": "@id" + }, + "refRoadSegment": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refRoadSegment", + "refSeeAlso": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refSeeAlso", + "refSmartPointOfInteraction": { + "@id": "https://uri.fiware.org/ns/data-models#refSmartPointOfInteraction", + "@type": "@id" + }, + "refSmartSpot": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refSmartSpot", + "refStreetlight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refStreetlight", + "refStreetlightControlCabinet": { + "@id": "https://uri.fiware.org/ns/data-models#refStreetlightControlCabinet", + "@type": "@id" + }, + "refStreetlightGroup": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refStreetlightGroup", + "refStreetlightModel": { + "@id": "https://uri.fiware.org/ns/data-models#refStreetlightModel", + "@type": "@id" + }, + "refTarget": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refTarget", + "refTargetDevice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refTargetDevice", + "refUser": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refUser", + "refUserDevice": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refUserDevice", + "refVehicleModel": { + "@id": "https://uri.fiware.org/ns/data-models#refVehicleModel", + "@type": "@id" + }, + "refWasteContainer": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/refWasteContainer", + "refWasteContainerIsle": { + "@id": "https://uri.fiware.org/ns/data-models#refWasteContainerIsle", + "@type": "@id" + }, + "refWasteContainerModel": { + "@id": "https://uri.fiware.org/ns/data-models#refWasteContainerModel", + "@type": "@id" + }, + "refWeatherObserved": { + "@id": "https://uri.fiware.org/ns/data-models#refWeatherObserved", + "@type": "@id" + }, + "refuseBin": "https://uri.fiware.org/ns/data-models#refuseBin", + "regulation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/regulation", + "relatedSource": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/relatedSource", + "relationships": { + "@id": "https://uri.etsi.org/ngsi-ld/relationships", + "@type": "@vocab" + }, + "relativeHumidity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/relativeHumidity", + "reliability": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reliability", + "religiousCentre": "https://uri.fiware.org/ns/data-models#religiousCentre", + "remainingDistance": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/remainingDistance", + "remainingTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/remainingTime", + "reportedAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reportedAt", + "reproductiveCondition": { + "@id": "https://uri.fiware.org/ns/data-models#reproductiveCondition", + "@type": "@vocab" + }, + "requiredPermit": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/requiredPermit", + "reservation": "https://uri.fiware.org/ns/data-models#reservation", + "reservationType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reservationType", + "reservoirHead": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reservoirHead", + "residence": "https://uri.fiware.org/ns/data-models#residence", + "residentPermit": "https://uri.fiware.org/ns/data-models#residentPermit", + "residential": "https://uri.fiware.org/ns/data-models#residential", + "responsible": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/responsible", + "restArea": "https://uri.fiware.org/ns/data-models#restArea", + "restaurant": "https://uri.fiware.org/ns/data-models#restaurant", + "result": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/result", + "retail": "https://uri.fiware.org/ns/data-models#retail", + "reversedLane": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/reversedLane", + "riding_hall": "https://uri.fiware.org/ns/data-models#riding_hall", + "river": "https://uri.fiware.org/ns/data-models#river", + "road": "https://uri.fiware.org/ns/data-models#road", + "roadClass": { + "@id": "https://uri.fiware.org/ns/data-models#roadClass", + "@type": "@vocab" + }, + "roadClosed": "https://uri.fiware.org/ns/data-models#roadClosed", + "roadSignalling": "https://uri.fiware.org/ns/data-models#roadSignalling", + "roadWorks": "https://uri.fiware.org/ns/data-models#roadWorks", + "robbery": "https://uri.fiware.org/ns/data-models#robbery", + "rodent": "https://uri.fiware.org/ns/data-models#rodent", + "roof": "https://uri.fiware.org/ns/data-models#roof", + "rooftop": "https://uri.fiware.org/ns/data-models#rooftop", + "root": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/root", + "rosarium": "https://uri.fiware.org/ns/data-models#rosarium", + "roughness": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/roughness", + "roundedLid": "https://uri.fiware.org/ns/data-models#roundedLid", + "routeCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/routeCode", + "routeColor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/routeColor", + "routeId": "https://uri.fiware.org/ns/data-models#routeId", + "routeSegments": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/routeSegments", + "routeSortOrder": "https://uri.fiware.org/ns/data-models#routeSortOrder", + "routeTextColor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/routeTextColor", + "routeType": { + "@id": "https://uri.fiware.org/ns/data-models#routeType", + "@type": "@vocab" + }, + "rssi": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/rssi", + "ruins": "https://uri.fiware.org/ns/data-models#ruins", + "sacredArt": "https://uri.fiware.org/ns/data-models#sacredArt", + "safeDeposit": "https://uri.fiware.org/ns/data-models#safeDeposit", + "salinity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/salinity", + "sanctuary": "https://uri.fiware.org/ns/data-models#sanctuary", + "saturday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/saturday", + "schedule": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/schedule", + "scheduled": "https://uri.fiware.org/ns/data-models#scheduled", + "school": "https://uri.fiware.org/ns/data-models#school", + "schoolTransportation": "https://uri.fiware.org/ns/data-models#schoolTransportation", + "scienceAndTechnology": "https://uri.fiware.org/ns/data-models#scienceAndTechnology", + "sculpturalGroups": "https://uri.fiware.org/ns/data-models#sculpturalGroups", + "sculpture": "https://uri.fiware.org/ns/data-models#sculpture", + "seasonTicket": "https://uri.fiware.org/ns/data-models#seasonTicket", + "secondary": "https://uri.fiware.org/ns/data-models#secondary", + "security": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/security", + "securityStaff": "https://uri.fiware.org/ns/data-models#securityStaff", + "seeAlso": "https://uri.fiware.org/ns/data-models#seeAlso", + "seeded": "https://uri.fiware.org/ns/data-models#seeded", + "selfConsumption": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/selfConsumption", + "semiautomatic": "https://uri.fiware.org/ns/data-models#semiautomatic", + "seminar": "https://uri.fiware.org/ns/data-models#seminar", + "sensing": "https://uri.fiware.org/ns/data-models#sensing", + "serialNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/serialNumber", + "service": "https://uri.fiware.org/ns/data-models#service", + "serviceArea": "https://uri.fiware.org/ns/data-models#serviceArea", + "serviceProvided": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/serviceProvided", + "serviceStatus": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/serviceStatus", + "setting": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/setting", + "severity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/severity", + "sex": { + "@id": "https://uri.fiware.org/ns/data-models#sex", + "@type": "@vocab" + }, + "sh2": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/sh2", + "shape": { + "@id": "https://uri.fiware.org/ns/data-models#shape", + "@type": "@vocab" + }, + "shed": "https://uri.fiware.org/ns/data-models#shed", + "sheds": "https://uri.fiware.org/ns/data-models#sheds", + "sheep": "https://uri.fiware.org/ns/data-models#sheep", + "shop": "https://uri.fiware.org/ns/data-models#shop", + "shoppingCentre": "https://uri.fiware.org/ns/data-models#shoppingCentre", + "shortName": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/shortName", + "shortRouteCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/shortRouteCode", + "shortStopCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/shortStopCode", + "shortTerm": "https://uri.fiware.org/ns/data-models#shortTerm", + "shower": "https://uri.fiware.org/ns/data-models#shower", + "showers": "https://uri.fiware.org/ns/data-models#showers", + "shrine": "https://uri.fiware.org/ns/data-models#shrine", + "sick": "https://uri.fiware.org/ns/data-models#sick", + "sideEntry": "https://uri.fiware.org/ns/data-models#sideEntry", + "sidewalk": "https://uri.fiware.org/ns/data-models#sidewalk", + "signLight": "https://uri.fiware.org/ns/data-models#signLight", + "signalStrength": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/signalStrength", + "singleLevel": "https://uri.fiware.org/ns/data-models#singleLevel", + "singleSpaceDetection": "https://uri.fiware.org/ns/data-models#singleSpaceDetection", + "siredBy": { + "@id": "https://uri.fiware.org/ns/data-models#siredBy", + "@type": "@id" + }, + "skilift": "https://uri.fiware.org/ns/data-models#skilift", + "smoke": "https://uri.fiware.org/ns/data-models#smoke", + "snail": "https://uri.fiware.org/ns/data-models#snail", + "snow": "https://uri.fiware.org/ns/data-models#snow", + "snowHeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/snowHeight", + "snow_ice": "https://uri.fiware.org/ns/data-models#snow_ice", + "so2": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/so2", + "socketNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/socketNumber", + "socketType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/socketType", + "softwareVersion": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/softwareVersion", + "soilMoisture": "https://uri.fiware.org/ns/data-models#soilMoisture", + "soilMoistureEC": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/soilMoistureEC", + "soilMoistureEc": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/soilMoistureEc", + "soilMoistureVwc": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/soilMoistureVwc", + "soilSalinity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/soilSalinity", + "soilTemperature": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/soilTemperature", + "solarPowerGenerated": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/solarPowerGenerated", + "solarRadiation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/solarRadiation", + "solarRadiaton": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/solarRadiaton", + "sonometerClass": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/sonometerClass", + "source": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/source", + "sourceCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/sourceCategory", + "spacesAvailable": "https://uri.fiware.org/ns/data-models#spacesAvailable", + "specialLocation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/specialLocation", + "specialTransport": "https://uri.fiware.org/ns/data-models#specialTransport", + "specialUsage": "https://uri.fiware.org/ns/data-models#specialUsage", + "specials": "https://uri.fiware.org/ns/data-models#specials", + "species": { + "@id": "https://uri.fiware.org/ns/data-models#species", + "@type": "@vocab" + }, + "specificFacility": "https://uri.fiware.org/ns/data-models#specificFacility", + "specificIdentifiedVehiclePermit": "https://uri.fiware.org/ns/data-models#specificIdentifiedVehiclePermit", + "speed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/speed", + "spring": "https://uri.fiware.org/ns/data-models#spring", + "square": "https://uri.fiware.org/ns/data-models#square", + "stable": "https://uri.fiware.org/ns/data-models#stable", + "stadium": "https://uri.fiware.org/ns/data-models#stadium", + "staffGuidesToSpace": "https://uri.fiware.org/ns/data-models#staffGuidesToSpace", + "staffed": "https://uri.fiware.org/ns/data-models#staffed", + "start": { + "@id": "https://uri.etsi.org/ngsi-ld/start", + "@type": "DateTime" + }, + "startDate": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/startDate", + "startKilometer": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/startKilometer", + "startPoint": "https://uri.fiware.org/ns/data-models#startPoint", + "startTime": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/startTime", + "startedAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/startedAt", + "startsAt": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/startsAt", + "static_caravan": "https://uri.fiware.org/ns/data-models#static_caravan", + "status": "https://uri.etsi.org/ngsi-ld/status", + "statusPercent": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/statusPercent", + "steady": "https://uri.fiware.org/ns/data-models#steady", + "steel": "https://uri.fiware.org/ns/data-models#steel", + "stopCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/stopCode", + "stopHeadsign": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/stopHeadsign", + "stopId": "https://uri.fiware.org/ns/data-models#stopId", + "stopSequence": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/stopSequence", + "storedWasteCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/storedWasteCode", + "storedWasteKind": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/storedWasteKind", + "storedWasteOrigin": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/storedWasteOrigin", + "streamGauge": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/streamGauge", + "streetCleaning": "https://uri.fiware.org/ns/data-models#streetCleaning", + "streetLighting": "https://uri.fiware.org/ns/data-models#streetLighting", + "strongWaves": "https://uri.fiware.org/ns/data-models#strongWaves", + "studentPermit": "https://uri.fiware.org/ns/data-models#studentPermit", + "sty": "https://uri.fiware.org/ns/data-models#sty", + "style": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/style", + "subCategory": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/subCategory", + "subscriptionId": { + "@id": "https://uri.etsi.org/ngsi-ld/subscriptionId", + "@type": "@id" + }, + "success": { + "@id": "https://uri.etsi.org/ngsi-ld/success", + "@type": "@id" + }, + "sumptuaryArts": "https://uri.fiware.org/ns/data-models#sumptuaryArts", + "sunLoungerRental": "https://uri.fiware.org/ns/data-models#sunLoungerRental", + "sunday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/sunday", + "sunshadeRental": "https://uri.fiware.org/ns/data-models#sunshadeRental", + "supportedProtocol": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/supportedProtocol", + "supportedUnits": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/supportedUnits", + "surfPracticeArea": "https://uri.fiware.org/ns/data-models#surfPracticeArea", + "surface": "https://uri.fiware.org/ns/data-models#surface", + "suspiciousAction": "https://uri.fiware.org/ns/data-models#suspiciousAction", + "sweepingMachine": "https://uri.fiware.org/ns/data-models#sweepingMachine", + "swellDirection": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/swellDirection", + "swellHeight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/swellHeight", + "swellPeriod": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/swellPeriod", + "switchingMode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/switchingMode", + "switchingOnHours": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/switchingOnHours", + "synagogue": "https://uri.fiware.org/ns/data-models#synagogue", + "tag": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/tag", + "tanker": "https://uri.fiware.org/ns/data-models#tanker", + "taulasTalayotsNavetas": "https://uri.fiware.org/ns/data-models#taulasTalayotsNavetas", + "taxi": "https://uri.fiware.org/ns/data-models#taxi", + "taxon": "https://uri.fiware.org/ns/data-models#taxon", + "tds": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/tds", + "telephone": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/telephone", + "temperature": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/temperature", + "temple": "https://uri.fiware.org/ns/data-models#temple", + "temporalQ": "https://uri.etsi.org/ngsi-ld/temporalQ", + "temporaryPrice": "https://uri.fiware.org/ns/data-models#temporaryPrice", + "terrace": "https://uri.fiware.org/ns/data-models#terrace", + "tertiary": "https://uri.fiware.org/ns/data-models#tertiary", + "textile": "https://uri.fiware.org/ns/data-models#textile", + "thdCurrent": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdCurrent", + "thdVoltage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdVoltage", + "thdrIntensity": "https://uri.fiware.org/ns/data-models#thdrIntensity", + "thdrIntensityR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrIntensityR", + "thdrIntensityS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrIntensityS", + "thdrIntensityT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrIntensityT", + "thdrVoltage": "https://uri.fiware.org/ns/data-models#thdrVoltage", + "thdrVoltageR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrVoltageR", + "thdrVoltageS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrVoltageS", + "thdrVoltageT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thdrVoltageT", + "theathre": "https://uri.fiware.org/ns/data-models#theathre", + "thematic": "https://uri.fiware.org/ns/data-models#thematic", + "themePark": "https://uri.fiware.org/ns/data-models#themePark", + "Thing": "https://garnet-framework.dev/data-models/models#Thing", + "throttling": "https://uri.etsi.org/ngsi-ld/throttling", + "thunderstorms": "https://uri.fiware.org/ns/data-models#thunderstorms", + "thursday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/thursday", + "time": { + "@id": "https://uri.etsi.org/ngsi-ld/time", + "@type": "DateTime" + }, + "timeInterval": "https://uri.etsi.org/ngsi-ld/timeInterval", + "timeStep": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/timeStep", + "timepoint": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/timepoint", + "timeproperty": "https://uri.etsi.org/ngsi-ld/timeproperty", + "timerel": "https://uri.etsi.org/ngsi-ld/timerel", + "timesSent": "https://uri.etsi.org/ngsi-ld/timesSent", + "timezone": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/timezone", + "title": "https://uri.etsi.org/ngsi-ld/title", + "toilet": "https://uri.fiware.org/ns/data-models#toilet", + "toilets": "https://uri.fiware.org/ns/data-models#toilets", + "toll": "https://uri.fiware.org/ns/data-models#toll", + "tollTerminal": "https://uri.fiware.org/ns/data-models#tollTerminal", + "tornado": "https://uri.fiware.org/ns/data-models#tornado", + "totalActiveEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalActiveEnergyExport", + "totalActiveEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalActiveEnergyImport", + "totalActivePower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalActivePower", + "totalApparentEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalApparentEnergyExport", + "totalApparentEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalApparentEnergyImport", + "totalApparentPower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalApparentPower", + "totalDisplacementPowerFactor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalDisplacementPowerFactor", + "totalLaneNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalLaneNumber", + "totalPowerFactor": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalPowerFactor", + "totalReactiveEnergyExport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalReactiveEnergyExport", + "totalReactiveEnergyImport": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalReactiveEnergyImport", + "totalReactivePower": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalReactivePower", + "totalSlotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalSlotNumber", + "totalSpotNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/totalSpotNumber", + "touristArea": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/touristArea", + "touristOffice": "https://uri.fiware.org/ns/data-models#touristOffice", + "tower": "https://uri.fiware.org/ns/data-models#tower", + "town": "https://uri.fiware.org/ns/data-models#town", + "tracked": "https://uri.fiware.org/ns/data-models#tracked", + "traffic": "https://uri.fiware.org/ns/data-models#traffic", + "trafficFlow": "https://uri.fiware.org/ns/data-models#trafficFlow", + "trafficJam": "https://uri.fiware.org/ns/data-models#trafficJam", + "trailer": "https://uri.fiware.org/ns/data-models#trailer", + "trainStation": "https://uri.fiware.org/ns/data-models#trainStation", + "train_station": "https://uri.fiware.org/ns/data-models#train_station", + "tram": "https://uri.fiware.org/ns/data-models#tram", + "transferType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/transferType", + "transformer_tower": "https://uri.fiware.org/ns/data-models#transformer_tower", + "transportation": "https://uri.fiware.org/ns/data-models#transportation", + "transportationType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/transportationType", + "transports": "https://uri.fiware.org/ns/data-models#transports", + "trashCan": "https://uri.fiware.org/ns/data-models#trashCan", + "triggerReason": "https://uri.etsi.org/ngsi-ld/triggerReason", + "trolley": "https://uri.fiware.org/ns/data-models#trolley", + "tropicalCyclone": "https://uri.fiware.org/ns/data-models#tropicalCyclone", + "truck": "https://uri.fiware.org/ns/data-models#truck", + "truckParking": "https://uri.fiware.org/ns/data-models#truckParking", + "trunk": "https://uri.fiware.org/ns/data-models#trunk", + "tss": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/tss", + "tsunami": "https://uri.fiware.org/ns/data-models#tsunami", + "tuesday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/tuesday", + "tunnel": "https://uri.fiware.org/ns/data-models#tunnel", + "turbidity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/turbidity", + "type": "@type", + "typeOfLocation": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/typeOfLocation", + "uVIndexMax": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/uVIndexMax", + "unchanged": "https://uri.etsi.org/ngsi-ld/unchanged", + "unclassified": "https://uri.fiware.org/ns/data-models#unclassified", + "underground": "https://uri.fiware.org/ns/data-models#underground", + "unesco": "https://uri.fiware.org/ns/data-models#unesco", + "unitCode": "https://uri.etsi.org/ngsi-ld/unitCode", + "universitary": "https://uri.fiware.org/ns/data-models#universitary", + "university": "https://uri.fiware.org/ns/data-models#university", + "unknown": "https://uri.fiware.org/ns/data-models#unknown", + "updated": "https://uri.etsi.org/ngsi-ld/updated", + "updatedAt": { + "@id": "https://uri.fiware.org/ns/data-models#updatedAt", + "@type": "https://uri.etsi.org/ngsi-ld/DateTime" + }, + "urban": "https://uri.fiware.org/ns/data-models#urban", + "urbanDeterrentParking": "https://uri.fiware.org/ns/data-models#urbanDeterrentParking", + "urbanTransit": "https://uri.fiware.org/ns/data-models#urbanTransit", + "urbanTreeSpot": "https://uri.fiware.org/ns/data-models#urbanTreeSpot", + "uri": "https://uri.etsi.org/ngsi-ld/uri", + "url": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/url", + "usageScenario": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/usageScenario", + "validFrom": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/validFrom", + "validTo": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/validTo", + "validity": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/validity", + "value": "https://uri.etsi.org/ngsi-ld/hasValue", + "values": { + "@id": "https://uri.etsi.org/ngsi-ld/hasValues", + "@container": "@list" + }, + "valveType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/valveType", + "van": "https://uri.fiware.org/ns/data-models#van", + "vehicleConfiguration": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleConfiguration", + "vehicleEngine": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleEngine", + "vehicleIdentificationNumber": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleIdentificationNumber", + "vehicleLift": "https://uri.fiware.org/ns/data-models#vehicleLift", + "vehicleModelDate": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleModelDate", + "vehicleOnRailTerminal": "https://uri.fiware.org/ns/data-models#vehicleOnRailTerminal", + "vehiclePlateIdentifier": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehiclePlateIdentifier", + "vehicleSpecialUsage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleSpecialUsage", + "vehicleSubType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleSubType", + "vehicleType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/vehicleType", + "vendingMachine": "https://uri.fiware.org/ns/data-models#vendingMachine", + "version": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/version", + "veryBad": "https://uri.fiware.org/ns/data-models#veryBad", + "veryGood": "https://uri.fiware.org/ns/data-models#veryGood", + "veryHigh": "https://uri.fiware.org/ns/data-models#veryHigh", + "virus": "https://uri.fiware.org/ns/data-models#virus", + "visibility": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/visibility", + "visitorPermit": "https://uri.fiware.org/ns/data-models#visitorPermit", + "volatileOrganicCompoundsTotal": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/volatileOrganicCompoundsTotal", + "voltage": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/voltage", + "voltageR": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/voltageR", + "voltageS": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/voltageS", + "voltageT": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/voltageT", + "volumeCurve": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/volumeCurve", + "wallCoeff": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/wallCoeff", + "walledArea": "https://uri.fiware.org/ns/data-models#walledArea", + "walls": "https://uri.fiware.org/ns/data-models#walls", + "warehouse": "https://uri.fiware.org/ns/data-models#warehouse", + "wasteContainerCleaning": "https://uri.fiware.org/ns/data-models#wasteContainerCleaning", + "wasteDisposal": "https://uri.fiware.org/ns/data-models#wasteDisposal", + "watchedAttributes": { + "@id": "https://uri.etsi.org/ngsi-ld/watchedAttributes", + "@type": "@vocab" + }, + "water": "https://uri.fiware.org/ns/data-models#water", + "water_dam": "https://uri.fiware.org/ns/data-models#water_dam", + "waterConsumption": "https://uri.fiware.org/ns/data-models#waterConsumption", + "waterCraftRental": "https://uri.fiware.org/ns/data-models#waterCraftRental", + "waterPollution": "https://uri.fiware.org/ns/data-models#waterPollution", + "waterSource": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/waterSource", + "water_tower": "https://uri.fiware.org/ns/data-models#water_tower", + "wateringFrequency": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/wateringFrequency", + "waveLength": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/waveLength", + "wax": "https://uri.fiware.org/ns/data-models#wax", + "weather": "https://uri.fiware.org/ns/data-models#weather", + "weatherConditions": "https://uri.fiware.org/ns/data-models#weatherConditions", + "weatherType": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/weatherType", + "wednesday": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/wednesday", + "weekly": "https://uri.fiware.org/ns/data-models#weekly", + "weight": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/weight", + "welfareCondition": { + "@id": "https://uri.fiware.org/ns/data-models#welfareCondition", + "@type": "@vocab" + }, + "wheelChairAccessible": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/wheelChairAccessible", + "wheelchairAccessible": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/wheelchairAccessible", + "wheelieBin": "https://uri.fiware.org/ns/data-models#wheelieBin", + "wheels": "https://uri.fiware.org/ns/data-models#wheels", + "whiteSand": "https://uri.fiware.org/ns/data-models#whiteSand", + "width": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/width", + "wifi": "https://uri.fiware.org/ns/data-models#wifi", + "wind": "https://uri.fiware.org/ns/data-models#wind", + "windDirection": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/windDirection", + "windSpeed": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/windSpeed", + "windy": "https://uri.fiware.org/ns/data-models#windy", + "withIncidence": "https://uri.fiware.org/ns/data-models#withIncidence", + "wood": "https://uri.fiware.org/ns/data-models#wood", + "workOrder": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/workOrder", + "workRecord": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/workRecord", + "working": "https://uri.fiware.org/ns/data-models#working", + "workingLife": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/workingLife", + "workingMode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/workingMode", + "xData": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/xData", + "yData": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/yData", + "yearly": "https://uri.fiware.org/ns/data-models#yearly", + "zen": "https://uri.fiware.org/ns/data-models#zen", + "zoneCode": "https://smart-data-models.github.io/data-models/terms.jsonld#/definitions/zoneCode" + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_time.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_time.png new file mode 100644 index 0000000..87cfd32 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_time.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_type.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_type.png new file mode 100644 index 0000000..e32544e Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/bucket_type.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cdkoutputs.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cdkoutputs.png new file mode 100644 index 0000000..63f4402 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cdkoutputs.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cfoutputs.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cfoutputs.png new file mode 100644 index 0000000..b046e69 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/cfoutputs.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deletething.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deletething.png new file mode 100644 index 0000000..de71fa5 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deletething.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow.png new file mode 100644 index 0000000..b075136 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow2.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow2.png new file mode 100644 index 0000000..de7d111 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadow2.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadowlist2.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadowlist2.png new file mode 100644 index 0000000..09e7e38 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/deviceshadowlist2.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/entities.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/entities.png new file mode 100644 index 0000000..e78c490 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/entities.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/geospatialbikes.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/geospatialbikes.png new file mode 100644 index 0000000..7af5610 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/geospatialbikes.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/getthing.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/getthing.png new file mode 100644 index 0000000..4e6d85f Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/getthing.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/iotbucket.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/iotbucket.png new file mode 100644 index 0000000..ec5a307 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/iotbucket.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/lambdavpc.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/lambdavpc.png new file mode 100644 index 0000000..6f9d8d4 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/lambdavpc.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/listthings.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/listthings.png new file mode 100644 index 0000000..b250f92 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/listthings.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/orion_arch.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/orion_arch.png new file mode 100644 index 0000000..9a6b53d Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/orion_arch.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/parameters.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/parameters.png new file mode 100644 index 0000000..c30f35a Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/parameters.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postdevice.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postdevice.png new file mode 100644 index 0000000..904cf25 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postdevice.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmandelentity.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmandelentity.png new file mode 100644 index 0000000..c0f284b Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmandelentity.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmangetentity.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmangetentity.png new file mode 100644 index 0000000..b45647b Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmangetentity.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanheader.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanheader.png new file mode 100644 index 0000000..6e80d61 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanheader.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanpostentity.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanpostentity.png new file mode 100644 index 0000000..7c48e8d Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postmanpostentity.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postshadow.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postshadow.png new file mode 100644 index 0000000..8ace196 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/postshadow.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/referencearch.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/referencearch.png new file mode 100644 index 0000000..88a561b Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/referencearch.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/scorpio_arch.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/scorpio_arch.png new file mode 100644 index 0000000..1bd3a57 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/scorpio_arch.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/shadowstate.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/shadowstate.png new file mode 100644 index 0000000..0105d9a Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/shadowstate.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stf-yt2.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stf-yt2.png new file mode 100644 index 0000000..b79b0f7 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stf-yt2.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stfiot_arch.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stfiot_arch.png new file mode 100644 index 0000000..279bd67 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/stfiot_arch.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/things.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/things.png new file mode 100644 index 0000000..220521c Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/things.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/upsert.png b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/upsert.png new file mode 100644 index 0000000..aa0d975 Binary files /dev/null and b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/docs/images/upsert.png differ diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/install.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/install.js new file mode 100644 index 0000000..24cf97f --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/install.js @@ -0,0 +1,54 @@ +const path = require('path') +const fs = require('fs') +const child_process = require('child_process') + +const root = process.cwd() +npm_install_recursive(root) + +// Since this script is intended to be run as a "preinstall" command, +// it will do `npm install` automatically inside the root folder in the end. +console.log('===================================================================') +console.log(`Performing "npm install" inside root folder`) +console.log('===================================================================') + +// Recurses into a folder +function npm_install_recursive(folder) +{ + const has_package_json = fs.existsSync(path.join(folder, 'package.json')) + + // If there is `package.json` in this folder then perform `npm install`. + // + // Since this script is intended to be run as a "preinstall" command, + // skip the root folder, because it will be `npm install`ed in the end. + // Hence the `folder !== root` condition. + // + if (has_package_json && folder !== root) + { + console.log('===================================================================') + console.log(`Performing "npm install" inside ${folder === root ? 'root folder' : './' + path.relative(root, folder)}`) + console.log('===================================================================') + + npm_install(folder) + } + + // Recurse into subfolders + for (let subfolder of subfolders(folder)) + { + npm_install_recursive(subfolder) + } +} + +// Performs `npm install` +function npm_install(where) +{ + child_process.execSync('npm install', { cwd: where, env: process.env, stdio: 'inherit' }) +} + +// Lists subfolders in a folder +function subfolders(folder) +{ + return fs.readdirSync(folder) + .filter(subfolder => fs.statSync(path.join(folder, subfolder)).isDirectory()) + .filter(subfolder => subfolder !== 'node_modules' && subfolder[0] !== '.') + .map(subfolder => path.join(folder, subfolder)) +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/jest.config.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/jest.config.js new file mode 100644 index 0000000..08263b8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/garnet-stack.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/garnet-stack.ts new file mode 100644 index 0000000..fd97479 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/garnet-stack.ts @@ -0,0 +1,56 @@ +import { CfnElement, CfnOutput, Names, Stack, StackProps } from 'aws-cdk-lib' +import { Construct } from 'constructs' +import { GarnetScorpio } from './stacks/garnet-scorpio/garnet-scorpio' +import { GarnetIotStack } from './stacks/garnet-iot/garnet-iot-stack' +import { Parameters } from '../parameters' +import { GarnetConstructs } from './stacks/garnet-constructs/garnet-constructs' + + + +export class GarnetStack extends Stack { + + + getLogicalId(element: CfnElement): string { + if (element?.node?.id?.includes('NestedStackResource')) { + return /([a-zA-Z0-9]+)\.NestedStackResource/.exec(element.node.id)![1] + } + return super.getLogicalId(element) + } + + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props) + + const garnet_constructs = new GarnetConstructs(this, 'CommonContructs', {}) + + let garnet_broker_stack = new GarnetScorpio(this, 'ContextBrokerProxy', { + vpc: garnet_constructs.vpc, + secret: garnet_constructs.secret + }) + + const garnet_iot_stack = new GarnetIotStack(this, 'GarnetIoT', { + dns_context_broker: Parameters.amazon_eks_cluster_load_balancer_dns, //garnet_broker_stack.dns_context_broker, + vpc: garnet_constructs.vpc, + api_ref: garnet_broker_stack.api_ref, + bucket_name: garnet_constructs.bucket_name, + az1: garnet_constructs.az1, + az2: garnet_constructs.az2 + }) + + new CfnOutput(this, 'GarnetEndpoint', { + value: garnet_broker_stack.broker_api_endpoint, + description: 'Garnet Unified API to access the Context Broker and Garnet IoT Capabilities' + }) + + new CfnOutput(this, 'GarnetPrivateSubEndpoint', { + value: garnet_iot_stack.private_sub_endpoint, + description: 'Garnet Private Notification Endpoint for Secured Subscriptions. Only accessible within the Garnet VPC' + }) + + new CfnOutput(this, 'GarnetIotQueueUrl', { + value: garnet_iot_stack.iot_sqs_endpoint_url, + description: 'Garnet IoT SQS Queue URL to connect your Data Producers' + }) + + + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package-lock.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package-lock.json new file mode 100644 index 0000000..125f2a8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package-lock.json @@ -0,0 +1,435 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodejs", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1426.0", + "axios": "^1.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1426.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1426.0.tgz", + "integrity": "sha512-qq4ydcRzQW2IqjMdCz5FklORREEtkSCJ2tm9CUJ2PaUOaljxpdxq9UI64vXiyRD+GIp5vdkmVNoTRi2rCXh3rA==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + } + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package.json new file mode 100644 index 0000000..b6289e8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/layers/nodejs/package.json @@ -0,0 +1,16 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1426.0", + "axios": "^1.4.0" + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/apigateway/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/apigateway/index.ts new file mode 100644 index 0000000..f457c3e --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/apigateway/index.ts @@ -0,0 +1,76 @@ +import { CfnOutput, Names} from "aws-cdk-lib" +import { CfnApi, CfnIntegration, CfnRoute, CfnStage, CfnVpcLink } from "aws-cdk-lib/aws-apigatewayv2" +import { SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2" +import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns" +import { Construct } from "constructs" +import { Parameters } from "../../../../parameters" + +export interface GarnetApiGatewayProps { + readonly vpc: Vpc, + readonly fargate_albListenerArn: string; // MODIFY TYPE TO STRING +} + +export class GarnetApiGateway extends Construct{ + public readonly api_ref: string + constructor(scope: Construct, id: string, props: GarnetApiGatewayProps) { + super(scope, id) + // Check props + if (!props.vpc){ + throw new Error('The property vpc is required to create an instance of GarnetApiGateway Construct') + } + // if (!props.fargate_alb){ + // throw new Error('The property fargate_alb is required to create an instance of GarnetApiGateway Construct') + // } + + const sg_vpc_link = new SecurityGroup(this, 'SgVpcLink', { + securityGroupName: `garnet-vpclink-sg-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + vpc: props.vpc + }) + + + + const vpc_link = new CfnVpcLink(this, 'VpcLink', { + name: `garnet-vpc-link-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + subnetIds: props.vpc.privateSubnets.map( (m) => m.subnetId), + securityGroupIds: [sg_vpc_link.securityGroupId] + }) + + const api = new CfnApi(this, 'HttpApi', { + name: `garnet-api`, + protocolType: 'HTTP', + corsConfiguration: { + allowHeaders: ['*'], + allowMethods: ['*'], + allowOrigins: ['*'] + }, + + + }) + + const stage = new CfnStage(this, 'StageApi', { + apiId: api.ref, + stageName: '$default', + autoDeploy: true + }) + + const integration = new CfnIntegration(this, 'HttpApiIntegration', { + apiId: api.ref, + integrationMethod: "ANY", + integrationType: "HTTP_PROXY", + connectionType: "VPC_LINK", + description: "API Integration", + connectionId: vpc_link.ref, + integrationUri: props.fargate_albListenerArn,//fargate_alb.listener.listenerArn, + payloadFormatVersion: "1.0", + }) + + const route = new CfnRoute(this, 'Route', { + apiId: api.ref, + routeKey: "ANY /{proxy+}", + target: `integrations/${integration.ref}` + }) + + this.api_ref = api.ref + + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/index.ts new file mode 100644 index 0000000..6a0d3c2 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/index.ts @@ -0,0 +1,56 @@ +import { CustomResource, Duration, Names } from "aws-cdk-lib"; +import { Runtime, Function, Code, Architecture } from "aws-cdk-lib/aws-lambda"; +import { Construct } from "constructs"; +import { Parameters } from "../../../../parameters"; +import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; +import { Provider } from "aws-cdk-lib/custom-resources"; +import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose"; +import { Bucket } from "aws-cdk-lib/aws-s3"; + +export interface GarnetBucketProps { + + } + + +export class GarnetBucket extends Construct { + + public readonly bucket_name: string + public readonly kinesis_firehose: CfnDeliveryStream + + constructor(scope: Construct, id: string, props: GarnetBucketProps) { + super(scope, id) + + // CUSTOM RESOURCE WITH A LAMBDA THAT WILL CREATE GARNET BUCKET AND ATHENA RESULTS BUCKET IF NOT EXISTS + const lambda_bucket_path = `${__dirname}/lambda/bucketHead` + const lambda_bucket = new Function(this, 'BucketHeadFunction', { + functionName: `garnet-utils-bucket-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Utils - Function that creates Garnet Bucket if it does not exist', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_bucket_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + BUCKET_NAME: Parameters.garnet_bucket + } + }) + + lambda_bucket.addToRolePolicy(new PolicyStatement({ + actions: ["s3:CreateBucket"], + resources: ["arn:aws:s3:::*"] + })) + + const bucket_provider = new Provider(this, 'CustomBucketProvider', { + onEventHandler: lambda_bucket, + providerFunctionName: `garnet-provider-custom-bucket-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + }) + + const bucket_resource = new CustomResource(this, 'CustomBucketProviderResource', { + serviceToken: bucket_provider.serviceToken, + + }) + + this.bucket_name = Parameters.garnet_bucket + + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/lambda/bucketHead/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/lambda/bucketHead/index.js new file mode 100644 index 0000000..b54842b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/bucket/lambda/bucketHead/index.js @@ -0,0 +1,33 @@ +const { S3Client, CreateBucketCommand } = require("@aws-sdk/client-s3") +const s3 = new S3Client() +const BUCKET_NAME = process.env.BUCKET_NAME + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='create' || request_type == 'update') { + + try { + await s3.send( + new CreateBucketCommand({ + Bucket: BUCKET_NAME + }) + ) + } catch (e) { + console.log(e.message) + } + + try { + console.log("UPDATE HERE") + await s3.send( + new CreateBucketCommand({ + Bucket: `${BUCKET_NAME}-athena-results` + }) + ) + } catch (e) { + console.log(e.message) + } + + return true + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/constants.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/constants.ts new file mode 100644 index 0000000..ee9f54b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/constants.ts @@ -0,0 +1,43 @@ +// List of AZs that support VPC links for HTTP APIs as https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vpc-links.html#http-api-vpc-link-availability +export const azlist: any = { + "us-east-2": ["use2-az1", "use2-az2", "use2-az3"], + "us-east-1": ["use1-az1", "use1-az2", "use1-az4", "use1-az5", "use1-az6"], + "us-west-1": ["usw1-az1", "usw1-az3"], + "us-west-2": ["usw2-az1", "usw2-az2", "usw2-az3", "usw2-az4"], + "ap-east-1": ["ape1-az2", "ape1-az3"], + "ap-south-1": ["aps1-az1", "aps1-az2", "aps1-az3"], + "ap-northeast-2": ["apne2-az1", "apne2-az2", "apne2-az3"], + "ap-southeast-1": ["apse1-az1", "apse1-az2", "apse1-az3"], + "ap-southeast-2": ["apse2-az1", "apse2-az2", "apse2-az3"], + "ap-northeast-1": ["apne1-az1", "apne1-az2", "apne1-az4"], + "ca-central-1": ["cac1-az1", "cac1-az2"], + "eu-central-1": ["euc1-az1", "euc1-az2", "euc1-az3"], + "eu-west-1": ["euw1-az1", "euw1-az2", "euw1-az3"], + "eu-west-2": ["euw2-az1", "euw2-az2", "euw2-az3"], + "eu-west-3": ["euw3-az1", "euw3-az3"], + "eu-north-1": ["eun1-az1", "eun1-az2", "eun1-az3"], + "me-south-1": ["mes1-az1", "mes1-az2", "mes1-az3"], + "sa-east-1": ["sae1-az1", "sae1-az2", "sae1-az3"], + "us-gov-west-1": ["usgw1-az1", "usgw1-az2", "usgw1-az3"] +} + +export const scorpiobroker_sqs_object = { + "SCORPIO_TOPICS_ENTITY": `garnet-scorpiobroker-entity`, + "SCORPIO_TOPICS_ENTITYBATCH": `garnet-scorpiobroker-entitybatch`, + "SCORPIO_TOPICS_REGISTRY": `garnet-scorpiobroker-registry`, + "SCORPIO_TOPICS_TEMPORAL": `garnet-scorpiobroker-temporal`, + "SCORPIO_TOPICS_INTERNALNOTIFICATION": `garnet-scorpiobroker-internalnotification`, + "SCORPIO_TOPICS_INTERNALREGSUB": `garnet-scorpiobroker-internalregsub`, +} + +export const garnet_constant = { + garnet_version: "1.0.0", // DO NOT CHANGE + shadow_prefix: "Garnet", + subAllName: 'GarnetDataLakeSub-DoNotDelete', + iotDomainName: 'garnet-iot-domain', + gluedbName: 'garnetdb' +} + +export enum Broker { + SCORPIO = "Scorpio" +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/garnet-constructs.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/garnet-constructs.ts new file mode 100644 index 0000000..699ddd8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/garnet-constructs.ts @@ -0,0 +1,40 @@ +import { NestedStack, NestedStackProps } from "aws-cdk-lib"; +import { Construct } from "constructs"; +import { GarnetSecret } from "./secret"; +import { GarnetNetworking } from "./networking"; +import { Utils } from "./utils"; +import { Vpc } from "aws-cdk-lib/aws-ec2"; +import { Secret } from "aws-cdk-lib/aws-secretsmanager"; +import { GarnetBucket } from "./bucket"; +import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose"; + + +export class GarnetConstructs extends NestedStack { + public readonly vpc: Vpc + public readonly secret: Secret + public readonly bucket_name: string + public readonly az1: string + public readonly az2: string + + constructor(scope: Construct, id: string, props?: NestedStackProps) { + super(scope, id, props); + + const utils_construct = new Utils(this, "Utils") + const bucket_construct = new GarnetBucket(this, 'Bucket', {}) + const secret_construct = new GarnetSecret(this, "Secret", {}) + const networking_construct = new GarnetNetworking(this, "Networking", { + az1: utils_construct.az1, + az2: utils_construct.az2 + }) + + networking_construct.node.addDependency(utils_construct) + + + this.az1 = utils_construct.az1, + this.az2 = utils_construct.az2 + this.vpc = networking_construct.vpc + this.secret = secret_construct.secret + this.bucket_name = bucket_construct.bucket_name + + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/networking/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/networking/index.ts new file mode 100644 index 0000000..991d89f --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/networking/index.ts @@ -0,0 +1,41 @@ +import { CfnSubnet, Port, SecurityGroup, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2" +import { Construct } from "constructs" +import { Parameters } from "../../../../parameters" +import { Aws, CfnOutput, Fn, Lazy, Names, Stack } from "aws-cdk-lib"; + +export interface GarnetNetworkingProps { + az1: string, + az2: string +} + +export class GarnetNetworking extends Construct { + public readonly vpc: Vpc + constructor(scope: Construct, id: string, props: GarnetNetworkingProps) { + super(scope, id) + const stack = Stack.of(this) + let broker_id = Parameters.garnet_broker + // VPC + const vpc = new Vpc(this, `VpcGarnet${broker_id}`, { + natGateways: 1, + availabilityZones: [`${props.az1}`,`${props.az2}`], + vpcName: `garnet-vpc-${broker_id.toLowerCase()}-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: `garnet-subnet-egress-${broker_id.toLowerCase()}`, + }, + { + subnetType: SubnetType.PRIVATE_ISOLATED, + name: `garnet-subnet-isolated-${broker_id.toLowerCase()}`, + }, + { + subnetType: SubnetType.PUBLIC, + name: `garnet-subnet-public-${broker_id.toLowerCase()}`, + }, + ], + }) + + + this.vpc = vpc; + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/index.ts new file mode 100644 index 0000000..dd7f0ba --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/index.ts @@ -0,0 +1,98 @@ +import { Aws, CfnOutput, Duration, Names } from "aws-cdk-lib" +import { EndpointType, LambdaRestApi } from "aws-cdk-lib/aws-apigateway" +import { InterfaceVpcEndpoint, Peer, Port, SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2" +import { AnyPrincipal, Effect, PolicyDocument, PolicyStatement } from "aws-cdk-lib/aws-iam" +import { Architecture, Code, Function, Runtime } from "aws-cdk-lib/aws-lambda" +import { Construct } from "constructs" + +export interface GarnetPrivateSubProps { + vpc: Vpc + } + + export class GarnetPrivateSub extends Construct { + + public readonly private_sub_endpoint: string + + constructor(scope: Construct, id: string, props: GarnetPrivateSubProps) { + super(scope, id) + + // SECURITY GROUP + const sg_garnet_vpc_endpoint = new SecurityGroup(this, 'PrivateSubSecurityGroup', { + securityGroupName: `garnet-private-sub-endpoint-sg-${Names.uniqueId(this).slice(-8).toLowerCase()}`, + vpc: props.vpc, + allowAllOutbound: true + }) + sg_garnet_vpc_endpoint.addIngressRule(Peer.anyIpv4(), Port.tcp(443)) + + // VPC ENDPOINT + const vpc_endpoint = new InterfaceVpcEndpoint(this, 'GarnetPrivateSubEndpoint', { + vpc: props.vpc, + service: { + name: `com.amazonaws.${Aws.REGION}.execute-api`, + port: 443 + }, + privateDnsEnabled: true, + securityGroups: [sg_garnet_vpc_endpoint] + }) + + // LAMBDA + const lambda_garnet_private_sub_path = `${__dirname}/lambda/garnetSub` + const lambda_garnet_private_sub = new Function(this, 'GarnetSubFunction', { + functionName: `garnet-private-sub-lambda-${Names.uniqueId(this).slice(-8).toLowerCase()}`, + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_garnet_private_sub_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION + } + }) + + lambda_garnet_private_sub.addToRolePolicy(new PolicyStatement({ + actions: ["iot:Publish"], + resources: [`arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/garnet/subscriptions/*`] + })) + + // POLICY + const api_policy = new PolicyDocument({ + statements: [ + new PolicyStatement({ + principals: [new AnyPrincipal], + actions: ['execute-api:Invoke'], + resources: ['execute-api:/*'], + effect: Effect.DENY, + conditions: { + StringNotEquals: { + "aws:SourceVpce": vpc_endpoint.vpcEndpointId + } + } + }), + new PolicyStatement({ + principals: [new AnyPrincipal], + actions: ['execute-api:Invoke'], + resources: ['execute-api:/*'], + effect: Effect.ALLOW + }) + ] + }) + + + const api_private_sub = new LambdaRestApi(this, 'ApiPrivateSub', { + restApiName:'garnet-private-sub-endpoint-api', + endpointTypes: [EndpointType.PRIVATE], + handler: lambda_garnet_private_sub, + policy: api_policy + }) + + this.private_sub_endpoint = api_private_sub.url + + new CfnOutput(this, 'ApiEndpoint', { + value: api_private_sub.url, + description: 'Private API Endpoint for Subscriptions' + }) + + + + } + } \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/lambda/garnetSub/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/lambda/garnetSub/index.js new file mode 100644 index 0000000..d61bcd4 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/privatesub/lambda/garnetSub/index.js @@ -0,0 +1,56 @@ +const iot_region = process.env.AWSIOTREGION +const { IoTDataPlaneClient, PublishCommand } = require("@aws-sdk/client-iot-data-plane") +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +exports.handler = async (event) => { + try { + const {body} = event + if(!body){ + return { + statusCode: 400, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: 'Bad Request. Notification is the only type valid'}) + } + } + const payload = JSON.parse(body) + if(payload?.type != "Notification") { + console.log('ERROR not Notification') + return { + statusCode: 400, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: 'Bad Request. Notification is the only type valid'}) + } + } + // GET THE SUBSCRIPTION NAME FROM SUBSCRIPTION ID + const subName = `${payload.subscriptionId.split(':').slice(-1)}` + const publish = await iotdata.send( + new PublishCommand({ + topic: `garnet/subscriptions/${subName}`, + payload: JSON.stringify(payload) + }) + ) + + const response = { + statusCode: 200 + } + return response + + } catch (e) { + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + + } + + +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/secret/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/secret/index.ts new file mode 100644 index 0000000..1dde7db --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/secret/index.ts @@ -0,0 +1,31 @@ +import { NestedStack, NestedStackProps } from "aws-cdk-lib" +import { Secret } from "aws-cdk-lib/aws-secretsmanager" +import { Construct } from "constructs" +import { Parameters } from "../../../../parameters" + + +export interface GarnetSecretProps { +} + +export class GarnetSecret extends Construct { + public readonly secret: Secret + + constructor(scope: Construct, id: string, props: GarnetSecretProps) { + super(scope, id) + + this.secret = new Secret(this, 'Secret', { + secretName:`garnet/secret/${Parameters.garnet_broker.toLowerCase()}/${this.node.addr.slice(-4)}`, + generateSecretString: { + secretStringTemplate: JSON.stringify({ + username: 'garnetadmin', + }), + excludePunctuation: true, + excludeCharacters: "/¥'%:;{}", + includeSpace: false, + generateStringKey: 'password' + } + }) + + } + +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/index.ts new file mode 100644 index 0000000..f12bd24 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/index.ts @@ -0,0 +1,141 @@ +import { Construct } from "constructs"; +import { azlist, scorpiobroker_sqs_object } from "../constants" +import { Aws, CfnOutput, CustomResource, Duration, Names, Stack } from "aws-cdk-lib"; +import { Code, Runtime, Function, Architecture } from "aws-cdk-lib/aws-lambda"; +import { PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Provider } from "aws-cdk-lib/custom-resources"; +import { Parameters } from "../../../../parameters"; + +export interface GarnetUtilProps {} + +export class Utils extends Construct { + + public readonly az1: string + public readonly az2: string + + constructor(scope: Construct, id: string, props?: GarnetUtilProps) { + super(scope, id) + + + // CHECK THE AZs TO DEPLOY GARNET + + if(Stack.of(this).region.startsWith('$')){ + throw new Error('Please type a valid region in the parameter.ts file') + } + + if(!azlist[`${Stack.of(this).region}`]){ + throw new Error('The stack is not yet available in the region selected') + } + + const compatible_azs = azlist[`${Stack.of(this).region}`] + + const get_az_func_path = `${__dirname}/lambda/getAzs` + const get_az_func = new Function(this, 'AzFunction', { + functionName: `garnet-utils-az-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Utils - Function that checks if which AZs the stack can be deployed for HTTP VPC Link and IoT VPC Endpoint service availability', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(get_az_func_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + COMPATIBLE_AZS: JSON.stringify(compatible_azs) + } + }) + get_az_func.addToRolePolicy(new PolicyStatement({ + actions: [ + "ec2:DescribeAvailabilityZones", + "ec2:DescribeVpcEndpointServices" + ], + resources: ['*'] + })) + + const get_az_provider = new Provider(this, 'getAzCleanUpprovider', { + onEventHandler: get_az_func, + providerFunctionName: `garnet-provider-utils-az-${Names.uniqueId(this).slice(-4).toLowerCase()}` + }) + + const get_az = new CustomResource(this, 'getAzCustomResource', { + serviceToken: get_az_provider.serviceToken + }) + + this.az1 = get_az.getAtt('az1').toString() + this.az2 = get_az.getAtt('az2').toString() + + // CLEAN SQS QUEUES CREATED BY SCORPIO BROKER + if(Parameters.garnet_broker == 'Scorpio'){ + + let sqs_urls = Object.values(scorpiobroker_sqs_object).map(q => `https://sqs.${Aws.REGION}.amazonaws.com/${Aws.ACCOUNT_ID}/${q}`) + + const scorpio_sqs_lambda_path = `${__dirname}/lambda/scorpioSqs` + const scorpio_sqs_lambda = new Function(this, 'ScorpioSqsFunction', { + functionName: `garnet-utils-scorpio-sqs-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Utils - Function that deletes the SQS Queue created by the Scorpio Context Broker', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(scorpio_sqs_lambda_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + SQS_QUEUES: JSON.stringify(sqs_urls) + } + }) + scorpio_sqs_lambda.addToRolePolicy(new PolicyStatement({ + actions: ["sqs:DeleteQueue"], + resources: [`arn:aws:sqs:${Aws.REGION}:${Aws.ACCOUNT_ID}:garnet-scorpiobroker-*`] + })) + + const scorpio_sqs_provider = new Provider(this, 'scorpioSqsProvider', { + onEventHandler: scorpio_sqs_lambda, + providerFunctionName: `garnet-provider-utils-scorpio-sqs-${Names.uniqueId(this).slice(-4).toLowerCase()}` + }) + + const scorpio_sqs_resource = new CustomResource(this, 'scorpioSqsCustomResource', { + serviceToken: scorpio_sqs_provider.serviceToken + }) + + } + + + // CLEAN INACTIVE GARNET TASK DEFINITION IN ECS + const clean_ecs_lambda_path = `${__dirname}/lambda/cleanTasks` + const clean_ecs_lambda = new Function(this, 'CleanEcsFunction', { + functionName: `garnet-utils-clean-ecs-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Utils - Function that removes unactive ECS task definitions', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(clean_ecs_lambda_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + } + }) + clean_ecs_lambda.addToRolePolicy(new PolicyStatement({ + actions: [ + "ecs:RegisterTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:DescribeTaskDefinition" + ], + resources: [`*`] + })) + + clean_ecs_lambda.addToRolePolicy(new PolicyStatement({ + actions: [ + "ecs:DeleteTaskDefinition" + ], + resources: [`arn:aws:ecs:${Aws.REGION}:${Aws.ACCOUNT_ID}:task-definition/Garnet*`] + })) + + const clean_ecs_provider = new Provider(this, 'cleanEcsProvider', { + onEventHandler: clean_ecs_lambda, + providerFunctionName: `garnet-provider-utils-clean-ecs-${Names.uniqueId(this).slice(-4).toLowerCase()}` + }) + + const scorpio_sqs_resource = new CustomResource(this, 'cleanEcsCustomResource', { + serviceToken: clean_ecs_provider.serviceToken + }) + + + + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/cleanTasks/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/cleanTasks/index.js new file mode 100644 index 0000000..e414f62 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/cleanTasks/index.js @@ -0,0 +1,33 @@ +const { ECSClient, ListTaskDefinitionsCommand, DeleteTaskDefinitionsCommand } = require("@aws-sdk/client-ecs") +const ecs = new ECSClient({}) + + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='delete') { + + try { + + const {taskDefinitionArns} = await ecs.send( + new ListTaskDefinitionsCommand({ + status: "INACTIVE" + }) + ) + const inactive_garnet_tasks = chunk(taskDefinitionArns.filter((task) => task.includes('Garnet')), 10) + for await (let task of inactive_garnet_tasks) { + await ecs.send( + new DeleteTaskDefinitionsCommand({ + taskDefinitions: task + }) + ) + + } + } catch (e) { + console.log(e) + } + return true + } +} + +const chunk = (arr, size) => Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => arr.slice(i * size, i * size + size)) diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/getAzs/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/getAzs/index.js new file mode 100644 index 0000000..c574483 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/getAzs/index.js @@ -0,0 +1,35 @@ +const { EC2Client, DescribeAvailabilityZonesCommand, DescribeVpcEndpointServicesCommand } = require("@aws-sdk/client-ec2") +const ec2 = new EC2Client({apiVersion: '2016-11-15'}) +const compatible_azs = JSON.parse(process.env.COMPATIBLE_AZS) +let region = process.env.AWS_REGION + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='create' || request_type == 'update') { + + + const {AvailabilityZones} = await ec2.send( + new DescribeAvailabilityZonesCommand({}) + ) + + const {ServiceDetails} = await ec2.send( + new DescribeVpcEndpointServicesCommand({ + ServiceNames: [`com.amazonaws.${region}.iot.data`] + }) + ) + + let vpc_link_az = AvailabilityZones.filter((az) => compatible_azs.includes(az.ZoneId)).map((az) => az.ZoneName) + let vpc_endpoint_iot_az = ServiceDetails[0]["AvailabilityZones"] + + let final_azs = vpc_link_az.filter((arr) => vpc_endpoint_iot_az.indexOf(arr) !== -1) + console.log({final_azs}) + + return { + Data: { + az1: final_azs[0], + az2: final_azs[final_azs.length - 1] + } + } + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/scorpioSqs/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/scorpioSqs/index.js new file mode 100644 index 0000000..6fff7c8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-constructs/utils/lambda/scorpioSqs/index.js @@ -0,0 +1,25 @@ +const { SQSClient, DeleteQueueCommand } = require("@aws-sdk/client-sqs") +const sqs = new SQSClient({}) +const SQS_QUEUES = JSON.parse(process.env.SQS_QUEUES) + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='delete') { + + try { + for await (let queue of Object.values(SQS_QUEUES)){ + await sqs.send( + new DeleteQueueCommand({ + QueueUrl: queue + }) + ) + + } + + } catch (e) { + console.log(e) + } + return true + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/index.ts new file mode 100644 index 0000000..1808e05 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/index.ts @@ -0,0 +1,420 @@ +import { Aws, Duration, Names } from "aws-cdk-lib" + +import { CfnIntegration, CfnRoute, CfnVpcLink } from "aws-cdk-lib/aws-apigatewayv2" +import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2" +import { PolicyStatement } from "aws-cdk-lib/aws-iam" +import { Runtime, Function, Code, CfnPermission, LayerVersion, Architecture } from "aws-cdk-lib/aws-lambda" +import { RetentionDays } from "aws-cdk-lib/aws-logs" +import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources" +import { Construct } from "constructs" +import { Parameters } from "../../../../parameters" +import { garnet_constant } from "../../garnet-constructs/constants" + + +export interface GarnetIotApiProps { + readonly api_ref: string, + readonly vpc: Vpc, + dns_context_broker: string +} + +export class GarnetIotApi extends Construct { + + constructor(scope: Construct, id: string, props: GarnetIotApiProps){ + super(scope, id) + + + // LAMBDA LAYER (SHARED LIBRARIES) + const layer_lambda_path = `./lib/layers` + const layer_lambda = new LayerVersion(this, 'LayerLambda', { + code: Code.fromAsset(layer_lambda_path), + compatibleRuntimes: [Runtime.NODEJS_18_X] + }) + + +// ********************************************** + + /** + * GARNET VERSION + */ + + // LAMBDA GARNET API VERSION + const lambda_garnet_version_path = `${__dirname}/lambda/garnetVersion` + const lambda_garnet_version = new Function(this, 'LambdaGarnetVersion', { + functionName: `garnet-api-version-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function that returns the Garnet Version', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_garnet_version_path), + handler: 'index.handler', + timeout: Duration.seconds(30), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + + environment: { + CONTEXT_BROKER: Parameters.garnet_broker, + GARNET_VERSION: garnet_constant.garnet_version + } + }) + + const garnet_version_integration = new CfnIntegration(this, 'GarnetVersionIntegration', { + apiId: props.api_ref, + integrationMethod: "GET", + integrationType: "AWS_PROXY", + integrationUri: lambda_garnet_version.functionArn, + connectionType: "INTERNET", + description: "GARNET VERSION INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const garnet_version_route = new CfnRoute(this, 'GarnetVersionRoute', { + apiId: props.api_ref, + routeKey: "GET /", + target: `integrations/${garnet_version_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionGarnetVersion', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_garnet_version.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /** + * END GARNET VERSION + */ + + +// ********************************************** + + + +// ********************************************** + + /** + * POST THING + */ + + // LAMBDA THAT POSTS THING + const lambda_post_thing_path = `${__dirname}/lambda/postThing` + const lambda_post_thing = new Function(this, 'LambdaPostThing', { + functionName: `garnet-iot-api-post-thing-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function to POST THING', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_post_thing_path), + handler: 'index.handler', + timeout: Duration.seconds(30), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix, + } + }) + + lambda_post_thing.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:UpdateThingGroup", + "iot:CreateThingGroup", + "iot:CreateThing", + "iot:AddThingToThingGroup", + "iot:UpdateThingGroupsForThing", + "iot:UpdateThingShadow" + ], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thinggroup/*`, + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*` + ] + })) + + const post_thing_integration = new CfnIntegration(this, 'postThingIntegration', { + apiId: props.api_ref, + integrationMethod: "POST", + integrationType: "AWS_PROXY", + integrationUri: lambda_post_thing.functionArn, + connectionType: "INTERNET", + description: "POST THING INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const post_thing_route = new CfnRoute(this, 'PostThingRoute', { + apiId: props.api_ref, + routeKey: "POST /iot/things", + target: `integrations/${post_thing_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionPostThing', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_post_thing.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /** + * END POST THING + */ + + +// ********************************************** + + /** + * DELETE THING + */ + + // LAMBDA THAT DELETE THING + const lambda_delete_thing_path = `${__dirname}/lambda/deleteThing` + const lambda_delete_thing = new Function(this, 'LambdaDeleteThing', { + functionName: `garnet-iot-api-delete-thing-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function to DELETE THING', + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_WITH_EGRESS + }, + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_delete_thing_path), + handler: 'index.handler', + timeout: Duration.seconds(30), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix, + DNS_CONTEXT_BROKER: props.dns_context_broker + } + }) + + lambda_delete_thing.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:DeleteThing", + "iot:DeleteThingShadow", + "iot:ListNamedShadowsForThing", + + ], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*` + ] + })) + + const delete_thing_integration = new CfnIntegration(this, 'deleteThingIntegration', { + apiId: props.api_ref, + integrationMethod: "DELETE", + integrationType: "AWS_PROXY", + integrationUri: lambda_delete_thing.functionArn, + connectionType: "INTERNET", + description: "DELETE THING INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const delete_thing_route = new CfnRoute(this, 'DeleteThingRoute', { + apiId: props.api_ref, + routeKey: "DELETE /iot/things/{thingName}", + target: `integrations/${delete_thing_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionDeleteThing', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_delete_thing.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /*** + * END DELETE THING + */ + +/************************************************************************** */ + + + /** + * GET THING + */ + + // LAMBDA THAT GETS THING + const lambda_get_thing_path = `${__dirname}/lambda/getThing` + const lambda_get_thing = new Function(this, 'LambdaGetThing', { + functionName: `garnet-iot-api-get-thing-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function to GET THING', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_get_thing_path), + handler: 'index.handler', + timeout: Duration.seconds(30), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix, + } + }) + + lambda_get_thing.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:GetThing", + "iot:listNamedShadowsForThing", + "iot:getThingShadow" + ], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*` + ] + })) + + const get_thing_integration = new CfnIntegration(this, 'getThingIntegration', { + apiId: props.api_ref, + integrationMethod: "GET", + integrationType: "AWS_PROXY", + integrationUri: lambda_get_thing.functionArn, + connectionType: "INTERNET", + description: "GET THING INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const get_thing_route = new CfnRoute(this, 'GetThingRoute', { + apiId: props.api_ref, + routeKey: "GET /iot/things/{thingName}", + target: `integrations/${get_thing_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionGetThing', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_get_thing.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /** + * END GET THING + */ + + /************************************************************************** */ + + + /** + * GET THINGS + */ + + // LAMBDA THAT GETS THING + const lambda_get_things_path = `${__dirname}/lambda/getThings` + const lambda_get_things = new Function(this, 'LambdaGetThings', { + functionName: `garnet-iot-api-get-things-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function to GET THINGS', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_get_things_path), + handler: 'index.handler', + timeout: Duration.minutes(3), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix, + } + }) + + lambda_get_things.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:searchIndex" + ], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:index/*` + ] + })) + + const get_things_integration = new CfnIntegration(this, 'getThingsIntegration', { + apiId: props.api_ref, + integrationMethod: "GET", + integrationType: "AWS_PROXY", + integrationUri: lambda_get_things.functionArn, + connectionType: "INTERNET", + description: "GET THINGS INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const get_things_route = new CfnRoute(this, 'GetThingsRoute', { + apiId: props.api_ref, + routeKey: "GET /iot/things", + target: `integrations/${get_things_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionGetThings', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_get_things.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /** + * END GET THINGS + */ + + + + /************************************************************************** */ + + + /** + * POST SHADOWS + */ + + // LAMBDA THAT POST SHADOWS + const lambda_post_shadows_path = `${__dirname}/lambda/postShadows` + const lambda_post_shadows = new Function(this, 'LambdaPostShadows', { + functionName: `garnet-iot-api-post-shadows-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet API - Function to POST SHADOW', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_post_shadows_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix, + } + }) + + lambda_post_shadows.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:UpdateThingShadow" + ], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*` + ] + })) + + const post_shadows_integration = new CfnIntegration(this, 'postShadowsIntegration', { + apiId: props.api_ref, + integrationMethod: "POST", + integrationType: "AWS_PROXY", + integrationUri: lambda_post_shadows.functionArn, + connectionType: "INTERNET", + description: "GET THING INTEGRATION", + payloadFormatVersion: "1.0", + }) + + const post_shadows_route = new CfnRoute(this, 'PostShadowsRoute', { + apiId: props.api_ref, + routeKey: "POST /iot/things/{thingName}/shadows", + target: `integrations/${post_shadows_integration.ref}` + }) + + new CfnPermission(this, 'ApiGatewayLambdaPermissionPostShadows', { + principal: `apigateway.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_post_shadows.functionName, + sourceArn: `arn:aws:execute-api:${Aws.REGION}:${Aws.ACCOUNT_ID}:${props.api_ref}/*/*/*` + }) + + /** + * END POST SHADOWS + */ + + + + + + + + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/deleteThing/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/deleteThing/index.js new file mode 100644 index 0000000..1828dd3 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/deleteThing/index.js @@ -0,0 +1,87 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX +const dns_broker = `http://${process.env.DNS_CONTEXT_BROKER}/ngsi-ld/v1` + +const axios = require('axios') +const { IoTDataPlaneClient, DeleteThingShadowCommand,ListNamedShadowsForThingCommand } = require("@aws-sdk/client-iot-data-plane") +const { IoTClient, DeleteThingCommand} = require("@aws-sdk/client-iot") +const iot = new IoTClient({region: iot_region}) +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +exports.handler = async (event) => { + + try { + const {pathParameters: {thingName}, headers, queryStringParameters} = event + + console.log(queryStringParameters?.['recursive']) + + if(!thingName) { + throw new Error('thingName is required') + } + + if (queryStringParameters?.['recursive'] == 'true'){ + try { + const {results} = await iotdata.send( + new ListNamedShadowsForThingCommand({thingName}) + ) + for await (let shadowName of results){ + if(shadowName.startsWith(shadow_prefix)){ + try { + await iotdata.send( + new DeleteThingShadowCommand({ thingName, shadowName }) + ) + let delete_entity = await axios.delete(`${dns_broker}/entities/urn:ngsi-ld:${shadowName.split(shadow_prefix)[1].split('-')[1]}:${thingName}`) + } catch (e) { + console.log(e.message) + } + + } + } + } catch(e){ + console.log(e.message) + } + + } else { + try { + await iotdata.send( + new DeleteThingShadowCommand({ + thingName, shadowName: `${shadow_prefix}-Thing` + }) + ) + let delete_entity = await axios.delete(`${dns_broker}/entities/urn:ngsi-ld:Thing:${thingName}`) + } catch (e) { + console.log(e.message) + } + } + + await iot.send( + new DeleteThingCommand({thingName}) + ) + let add = '' + if(queryStringParameters?.['recursive'] == 'true'){ + add = 'and all its associated entities' + } + + let msg = `Successfully deleted the thing ${thingName} ${add}` + + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: msg}), + } + return response + } + catch(e){ + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/garnetVersion/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/garnetVersion/index.js new file mode 100644 index 0000000..1d909a5 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/garnetVersion/index.js @@ -0,0 +1,60 @@ +const axios = require('axios') +const CONTEXT_BROKER = process.env.CONTEXT_BROKER +const GARNET_VERSION = process.env.GARNET_VERSION + + +exports.handler = async (event) => { + + try { + + const {headers : {Host}} = event + let path + if(CONTEXT_BROKER == "Orion"){ + path = `ngsi-ld/ex/v1/version` + } else if (CONTEXT_BROKER == "Scorpio") { + path = `q/info` + } else { + throw new Error(`${CONTEXT_BROKER} is an invalid value for Context Broker`) + } + let url = `https://${Host}/${path}` + console.log(url) + let dt =null + try { + let {data} = await axios.get(url) + dt= data + } catch (e) { + console.log(e) + } + + + let result = { + "garnet_version": GARNET_VERSION, + "context_broker": CONTEXT_BROKER, + "context_broker_info": dt + } + + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(result), + } + console.log(response) + + return response + + + } catch (e) { + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } + +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThing/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThing/index.js new file mode 100644 index 0000000..f905964 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThing/index.js @@ -0,0 +1,83 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX + +const { IoTDataPlaneClient, ListNamedShadowsForThingCommand, GetThingShadowCommand } = require("@aws-sdk/client-iot-data-plane") +const { IoTClient, DeleteThingCommand} = require("@aws-sdk/client-iot") +const iot = new IoTClient({region: iot_region}) +const iotdata = new IoTDataPlaneClient({region: iot_region}) +const { toUtf8 } = require('@aws-sdk/util-utf8-browser') + +exports.handler = async (event) => { + + try { + const {pathParameters: {thingName}, headers, queryStringParameters} = event + + let shadows = null + if(!thingName) { + throw new Error('thingName is required') + } + if(queryStringParameters && 'shadows' in queryStringParameters) { + shadows = queryStringParameters['shadows'].split(',').map((shadow) => `${shadow_prefix}-${shadow.trim()}`) + + } + console.log({queryStringParameters}) + + let {results} = await iotdata.send( + new ListNamedShadowsForThingCommand({ thingName }) + ) + + if(!results.includes(`${shadow_prefix}-Thing`)) { + return { + statusCode: 404, + body: JSON.stringify({ + message: `${thingName} is not registered in the Garnet IoT registry.` + }) + } + } + console.log({results}) + if(shadows){ + results = results.filter(result => shadows.includes(result)) + } + + console.log({shadows}) + console.log({results}) + + let result = { + thingName, + entities: [] + } + + for await (let shadowName of results) { + + let {payload} = await iotdata.send( + new GetThingShadowCommand({ thingName, shadowName}) + ) + + result.entities.push( + JSON.parse( toUtf8(payload) ).state.reported + ) + } + + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(result), + } + console.log(response) + + return response + + } catch(e){ + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThings/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThings/index.js new file mode 100644 index 0000000..857f787 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/getThings/index.js @@ -0,0 +1,84 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX + +const { IoTClient, SearchIndexCommand} = require("@aws-sdk/client-iot") +const iot = new IoTClient({region: iot_region}) + + + +exports.handler = async (event) => { + + try { + let { headers, queryStringParameters} = event + + let index = 'Thing' + + if(queryStringParameters && queryStringParameters['index']) { + index = queryStringParameters['index'] + } + + // lower case headers + headers = Object.keys(headers).reduce( (acc, key) => { + acc[key.toLowerCase()] = headers[key] + return acc + }, {}) + + let nToken = headers?.['nexttoken'] ? headers['nexttoken'] : null + + let {things, nextToken} = await iot.send( + new SearchIndexCommand({ + nextToken: nToken, + queryString: `thingName:*`, + indexName: 'AWS_Things' + }) + ) + + let tgs = things.reduce((prev, curr) => { + return [ ...prev, + { thingName: curr.thingName, + thingGroupNames: curr.thingGroupNames, + thingTypeName: curr.thingTypeName + } + ] + }, []) + + + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + nextToken, + things: tgs + }), + } + + + return response + + } catch(e){ + + if(e.statusCode == 400){ + let statusCode = e.statusCode ? e.statusCode : 500 + const response = { + statusCode, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: `Error with the submitted index. Please verify it is a valid index and registered in the Garnet IoT Index`}), + } + } + + let statusCode = e.statusCode ? e.statusCode : 500 + const response = { + statusCode, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postShadows/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postShadows/index.js new file mode 100644 index 0000000..6886092 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postShadows/index.js @@ -0,0 +1,111 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX + +const { IoTDataPlaneClient, UpdateThingShadowCommand } = require("@aws-sdk/client-iot-data-plane") +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +exports.handler = async (event) => { + + try{ + try { JSON.parse(event.body) } catch(e){throw new Error('Invalid body property. The body must be a JSON object. Check the Content-Type in your header')} + + const {body, pathParameters: {thingName}} = event + + if(!thingName) { + throw new Error('The thingName is required') + } + + if(!body){ + throw new Error('An NGSI-LD entity is required in the body') + } + + let entity = JSON.parse(body) + console.log({entity}) + + if(!entity.id || !entity.type) { + throw new Error('id and type are required') + } + + const entity_thingName = `${entity.id.split(':').slice(-1)}` + + if(entity_thingName != thingName){ + throw new Error(`The name ${entity_thingName} extracted from the id of the entity does not match ${thingName}`) + } + if(entity.type == 'Thing') { + throw new Error(`Type Thing is not a valid type for this operation`) + } + if(!entity.id.startsWith(`urn:ngsi-ld:${entity.type}:`)){ + throw new Error(`Invalid id. The id must start with urn:ngsi-ld:${entity.type}: following with thingName`) + } + + for (let [key, value] of Object.entries(entity)) { + if(!['type', 'id', '@context'].includes(key)) { + + if( typeof value == 'object' && !Array.isArray(value)){ + recursive_concise(key, value) + } else { + entity[key] = { + value: value + } + } + + + } + } + + if (entity['@context']) { + console.log(`Removed the context:`) + console.log(entity['@context']) + delete entity['@context'] + } + + const shadow_payload = { + state: { + reported: entity + } + } + + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({payload: JSON.stringify(shadow_payload ), thingName: thingName, shadowName: `${shadow_prefix}-${entity.type}`}) + ) + + let msg = `The entity ${entity.id} successfully registered as ${shadow_prefix}-${entity.type} for ${thingName}` + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: msg}), + } + return response + + } catch(e){ + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } + + + + +} + + + +const recursive_concise = (key, value) => { + + if( typeof value == 'object' && !Array.isArray(value)){ + if(['Property', 'Relationship', 'GeoProperty'].includes(value['type'])) { + delete value['type'] + } + for (let [skey, svalue] of Object.entries(value)){ + recursive_concise(skey,svalue) + } + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postThing/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postThing/index.js new file mode 100644 index 0000000..9da0459 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-api/lambda/postThing/index.js @@ -0,0 +1,138 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX + +const { IoTDataPlaneClient, UpdateThingShadowCommand } = require("@aws-sdk/client-iot-data-plane") +const { IoTClient, CreateThingCommand, CreateThingGroupCommand, AddThingToThingGroupCommand } = require("@aws-sdk/client-iot") +const iot = new IoTClient({region: iot_region}) +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +exports.handler = async (event) => { + + try{ + + try { JSON.parse(event.body) } catch(e){throw new Error('Invalid body property. The body must be a JSON object. Check the Content-Type in your header')} + + const body = JSON.parse(event.body) + let payload = body + let groups = body.thingGroups?.value + if (groups) { + if(!Array.isArray(groups)) { + throw new Error('Invalid thingGroups property. The type must be property and value must be an array') + } + + if(groups.length > 10){ + throw new Error('A thing can be added to a maximum of 10 thing groups') + } + } + + if(!payload.id || !payload.type) { + throw new Error('id and type are required') + } + + if(payload.type != 'Thing'){ + throw new Error('Invalid type. The value of type must be Thing') + } + + if(!payload.id.startsWith('urn:ngsi-ld:Thing:')){ + throw new Error('Invalid id. The id must start with urn:ngsi-ld:Thing: following with thingName') + } + + // Get the thingName from the id + const thingName = `${payload.id.split(':').slice(-1)}` + + if(!thingName){ + throw new Error('Invalid thingName') + } + + // Create the thing + const thing_in_registry = await iot.send( + new CreateThingCommand({ thingName: thingName }) + ) + + // if a group is specified + if(groups && groups.length > 0){ + for await (let group of groups){ + const thing_group = await iot.send( + new CreateThingGroupCommand({thingGroupName: group}) + ) + const thing_in_group = await iot.send( + new AddThingToThingGroupCommand({thingName: thingName, thingGroupName: group}) + ) + } + } + + console.log(thing_in_registry) + + + for (let [key, value] of Object.entries(payload)) { + if(!['type', 'id', '@context'].includes(key)) { + + if( typeof value == 'object' && !Array.isArray(value)){ + recursive_concise(key, value) + } else { + payload[key] = { + value: value + } + } + + + } + } + + if (payload['@context']) { + console.log(`Removed the context:`) + console.log(payload['@context']) + delete payload['@context'] + } + + const shadow_payload = { + state: { + reported: payload + } + } + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({payload: JSON.stringify(shadow_payload ), thingName: thingName, shadowName: `${shadow_prefix}-${payload.type}`}) + ) + + let msg = `${thingName} successfully registered` + if (groups) { + msg = `${msg} and added to ${JSON.stringify(groups)}` + } + + const response = { + statusCode: 200, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: msg}), + } + return response + + } catch(e){ + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + } + + + + +} + +const recursive_concise = (key, value) => { + + if( typeof value == 'object' && !Array.isArray(value)){ + if(['Property', 'Relationship', 'GeoProperty'].includes(value['type'])) { + delete value['type'] + } + for (let [skey, svalue] of Object.entries(value)){ + recursive_concise(skey,svalue) + } + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/index.ts new file mode 100644 index 0000000..7eddae8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/index.ts @@ -0,0 +1,317 @@ +import { Aws, Duration, Names } from "aws-cdk-lib"; +import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal, +} from "aws-cdk-lib/aws-iam"; +import { CfnTopicRule } from "aws-cdk-lib/aws-iot"; +import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose"; +import { + Code, + LayerVersion, + Runtime, + Function, + Architecture, +} from "aws-cdk-lib/aws-lambda"; +import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import { RetentionDays } from "aws-cdk-lib/aws-logs"; +import { Bucket } from "aws-cdk-lib/aws-s3"; +import { Topic } from "aws-cdk-lib/aws-sns"; +import { SqsSubscription } from "aws-cdk-lib/aws-sns-subscriptions"; +import { Queue } from "aws-cdk-lib/aws-sqs"; +import { + AwsCustomResource, + AwsCustomResourcePolicy, + PhysicalResourceId, +} from "aws-cdk-lib/custom-resources"; +import { Construct } from "constructs"; +import { Parameters } from "../../../../parameters"; + +export interface GarnetIotprops { + dns_context_broker: string; + vpc: Vpc; + bucket_arn: string; +} + +export class GarnetIot extends Construct { + public readonly sqs_garnet_iot_arn: string; + public readonly sns_garnet_iot: Topic; + + constructor(scope: Construct, id: string, props: GarnetIotprops) { + super(scope, id); + + //CHECK PROPS + if (!props.vpc) { + throw new Error( + "The property vpc is required to create an instance of GarnetIot Construct" + ); + } + if (!props.dns_context_broker) { + throw new Error( + "The property dns_context_broker is required to create an instance of GarnetIot Construct" + ); + } + if (!props.bucket_arn) { + throw new Error( + "The property bucket_arn is required to create an instance of GarnetIot Construct" + ); + } + + // IoT DATALAKE BUCKET + const bucket = Bucket.fromBucketArn(this, "IoTBucket", props.bucket_arn); + + // LAMBDA LAYER (SHARED LIBRARIES) + const layer_lambda_path = `./lib/stacks/garnet-iot/layers`; + const layer_lambda = new LayerVersion(this, "LayerLambda", { + code: Code.fromAsset(layer_lambda_path), + compatibleRuntimes: [Runtime.NODEJS_18_X], + }); + + // SQS ENTRY POINT + const sqs_garnet_endpoint = new Queue(this, "SqsGarnetIot", { + queueName: `garnet-iot-queue-${Aws.REGION}`, + }); + this.sqs_garnet_iot_arn = sqs_garnet_endpoint.queueArn; + + // LAMBDA TO UPDATE DEVICE SHADOW + const lambda_update_shadow_path = `${__dirname}/lambda/updateShadow`; + const lambda_update_shadow = new Function(this, "LambdaUpdateShadow", { + functionName: `garnet-iot-update-shadow-lambda-${Names.uniqueId(this) + .slice(-8) + .toLowerCase()}`, + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_update_shadow_path), + handler: "index.handler", + timeout: Duration.seconds(15), + logRetention: RetentionDays.THREE_MONTHS, + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: Parameters.garnet_iot.shadow_prefix + }, + }); + + // ADD PERMISSION FOR LAMBDA THAT UPDATES SHADOW TO ACCESS SQS ENTRY POINT + lambda_update_shadow.addToRolePolicy( + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + resources: [`${sqs_garnet_endpoint.queueArn}`], + }) + ); + + // ADD PERMISSION TO ACCESS AWS IoT DEVICE SHADOW + lambda_update_shadow.addToRolePolicy( + new PolicyStatement({ + actions: ["iot:UpdateThingShadow"], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*/${Parameters.garnet_iot.shadow_prefix}-*`, + ], + }) + ); + + // ADD THE SQS ENTRY POINT AS EVENT SOURCE FOR LAMBDA + lambda_update_shadow.addEventSource( + new SqsEventSource(sqs_garnet_endpoint, { batchSize: 10 }) + ); + + // SQS TO LAMBDA CONTEXT BROKER + const sqs_to_context_broker = new Queue(this, "SqsToLambdaContextBroker", { + queueName: `garnet-iot-contextbroker-queue-${Aws.REGION}` + }); + + // ROLE THAT GRANTS ACCESS TO FIREHOSE TO READ/WRITE BUCKET + const role_firehose = new Role(this, "FirehoseRole", { + assumedBy: new ServicePrincipal("firehose.amazonaws.com"), + }); + bucket.grantReadWrite(role_firehose); + + // KINESIS FIREHOSE DELIVERY STREAM + const kinesis_firehose = new CfnDeliveryStream( + this, + "KinesisFirehoseDeliveryGarnetIotDataLake", + { + deliveryStreamName: `garnet-iot-firehose-stream-${Names.uniqueId(this).slice(-8).toLowerCase()}`, + deliveryStreamType: "DirectPut", + extendedS3DestinationConfiguration: { + bucketArn: bucket.bucketArn, + roleArn: role_firehose.roleArn, + bufferingHints: { + intervalInSeconds: 60, + sizeInMBs: 64, + }, + processingConfiguration: { + enabled: true, + processors: [ + { + type: "MetadataExtraction", + parameters: [ + { + parameterName: "MetadataExtractionQuery", + parameterValue: "{type:.type}", + }, + { + parameterName: "JsonParsingEngine", + parameterValue: "JQ-1.6", + }, + ], + }, + ], + }, + dynamicPartitioningConfiguration: { + enabled: true, + }, + prefix: `type=!{partitionKeyFromQuery:type}/dt=!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + errorOutputPrefix: `type=!{firehose:error-output-type}/dt=!{timestamp:yyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + }, + } + ); + + // ROLE THAT GRANT ACCESS TO IOT RULE TO ACTIONS + const iot_rule_actions_role = new Role(this, "RoleGarnetIotRuleIngestion", { + assumedBy: new ServicePrincipal("iot.amazonaws.com"), + }); + iot_rule_actions_role.addToPolicy( + new PolicyStatement({ + resources: [ + `${sqs_to_context_broker.queueArn}`, + `${kinesis_firehose.attrArn}`, + ], + actions: [ + "sqs:SendMessage", + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams", + "firehose:ListTagsForDeliveryStream", + "firehose:PutRecord", + "firehose:PutRecordBatch", + ], + }) + ); + + // IOT RULE THAT LISTENS TO CHANGES IN GARNET SHADOWS AND PUSH TO SQS + const iot_rule = new CfnTopicRule(this, "IoTRuleShadows", { + ruleName: `garnet_iot_rule_${Names.uniqueId(this).slice(-8).toLowerCase()}`, + topicRulePayload: { + awsIotSqlVersion: "2016-03-23", + ruleDisabled: false, + sql: `SELECT current.state.reported.* + FROM '$aws/things/+/shadow/name/+/update/documents' + WHERE startswith(topic(6), '${Parameters.garnet_iot.shadow_prefix}') + AND NOT isUndefined(current.state.reported.type)`, + actions: [ + { + sqs: { + queueUrl: sqs_to_context_broker.queueUrl, + roleArn: iot_rule_actions_role.roleArn, + }, + }, + { + firehose: { + deliveryStreamName: kinesis_firehose.ref, + roleArn: iot_rule_actions_role.roleArn, + separator: "\n", + }, + }, + ], + }, + }) + + + // IOT RULE THAT LISTENS TO SUBSCRIPTIONS AND PUSH TO FIREHOSE + const iot_rule_sub = new CfnTopicRule(this, "IotRuleSub", { + ruleName: `garnet_subscriptions_rule_${Names.uniqueId(this).slice(-8).toLowerCase()}`, + topicRulePayload: { + awsIotSqlVersion: "2016-03-23", + ruleDisabled: false, + sql: `SELECT * FROM 'garnet/subscriptions/+'`, + actions: [ + { + firehose: { + deliveryStreamName: kinesis_firehose.ref, + roleArn: iot_rule_actions_role.roleArn, + separator: "\n", + }, + }, + ], + }, + }) + + + + + // LAMBDA THAT GETS MESSAGES FROM THE QUEUE AND UPDATES CONTEXT BROKER + const lambda_to_context_broker_path = `${__dirname}/lambda/updateContextBroker`; + const lambda_to_context_broker = new Function( + this, + "LambdaUpdateContextBroker", + { + functionName: `garnet-iot-update-broker-lambda-${Names.uniqueId(this) + .slice(-8) + .toLowerCase()}`, + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }, + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_to_context_broker_path), + handler: "index.handler", + timeout: Duration.seconds(15), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + DNS_CONTEXT_BROKER: props.dns_context_broker, + URL_SMART_DATA_MODEL: Parameters.garnet_iot.smart_data_model_url, + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: Parameters.garnet_iot.shadow_prefix + }, + } + ); + + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses", + ], + resources: ["*"], + }) + ); + + // ADD PERMISSION FOR LAMBDA TO ACCESS SQS + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + resources: [`${sqs_to_context_broker.queueArn}`], + }) + ); + + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: ["iot:UpdateThingShadow", "iot:GetThingShadow"], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*/${Parameters.garnet_iot.shadow_prefix}-*`, + ], + }) + ); + + lambda_to_context_broker.addEventSource( + new SqsEventSource(sqs_to_context_broker, { batchSize: 10 }) + ); + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateContextBroker/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateContextBroker/index.js new file mode 100644 index 0000000..6e8d6e8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateContextBroker/index.js @@ -0,0 +1,89 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX +const dns_broker = `http://${process.env.DNS_CONTEXT_BROKER}/ngsi-ld/v1` +const URL_SMART_DATA_MODEL = process.env.URL_SMART_DATA_MODEL + +const {IoTDataPlaneClient, UpdateThingShadowCommand, GetThingShadowCommand} = require('@aws-sdk/client-iot-data-plane') +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +const axios = require('axios') + + + + +exports.handler = async (event, context) => { + + try { + let entities = [] + + for await (let msg of event.Records){ + let payload = JSON.parse(msg.body) + const thingName = `${payload.id.split(':').slice(-1)}` + if(!payload.id || !payload.type){ + throw new Error('Invalid entity: id or type is missing') + } + + // Check if location property is in the payload. If not, get it from the Garnet-Device named shadow + if(!payload.location && payload.type != 'Device') { + + try { + let {payload : device_shadow} = await iotdata.send( + new GetThingShadowCommand({ + thingName: thingName, + shadowName: `${shadow_prefix}-Device` + }) + ) + + device_shadow = JSON.parse( + new TextDecoder('utf-8').decode(device_shadow) + ) + payload.location = device_shadow.state.reported.location + + if(payload.location){ + const shadow_payload = { + state: { + reported: payload + } + } + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({ + payload: JSON.stringify(shadow_payload), + thingName: thingName, + shadowName: `${shadow_prefix}-${payload.type}` + }) + ) + } + + + + } catch (e) { + console.log(e.message) + } + } + if (payload.raw) delete payload.raw + entities.push(payload) + } + const headers = { + 'Content-Type': 'application/json', + 'Link': `<${URL_SMART_DATA_MODEL}>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"` + } + try { + let upsert = await axios.post(`${dns_broker}/entityOperations/upsert`, entities, {headers: headers}) + } catch (e) { + log_error(event,context, e.message, e) + } + } catch (e) { + log_error(event,context, e.message, e) + } +} + + +const log_error = (event, context, message, error) => { + console.error(JSON.stringify({ + message: message, + event: event, + error: error, + context: context + })) +} + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateShadow/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateShadow/index.js new file mode 100644 index 0000000..052e3e1 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-core/lambda/updateShadow/index.js @@ -0,0 +1,54 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX +const url_broker = process.env.URL_CONTEXT_BROKER + +const {IoTDataPlaneClient, UpdateThingShadowCommand} = require('@aws-sdk/client-iot-data-plane') +const iotdata = new IoTDataPlaneClient({region: iot_region}) + + +exports.handler = async (event, context) => { + try { + + for await (let msg of event.Records){ + let payload = JSON.parse(msg.body) + + if(!payload.id || !payload.type){ + throw new Error('Invalid entity - id or type is missing') + } + + const thingName = `${payload.id.split(':').slice(-1)}` + + try { + const shadow_payload = { + state: { + reported: payload + } + } + + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({ + payload: JSON.stringify(shadow_payload), + thingName: thingName, + shadowName: `${shadow_prefix}-${payload.type}` + }) + ) + + } catch (e) { + log_error(event,context, e.message, e) + } + + } + + } catch (e) { + log_error(event,context, e.message, e) + } +} + +const log_error = (event, context, message, error) => { + console.error(JSON.stringify({ + message: message, + event: event, + error: error, + context: context + })) +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/index.ts new file mode 100644 index 0000000..bd8fe31 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/index.ts @@ -0,0 +1,214 @@ +import { Aws, Duration, Names } from "aws-cdk-lib"; +import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; +import { ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; +import { CfnTopicRule } from "aws-cdk-lib/aws-iot"; +import { Code, LayerVersion, Runtime, Function,Architecture} from "aws-cdk-lib/aws-lambda"; +import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import { RetentionDays } from "aws-cdk-lib/aws-logs"; +import { Topic } from "aws-cdk-lib/aws-sns"; +import { Queue } from "aws-cdk-lib/aws-sqs"; +import { Construct } from "constructs"; +import { Parameters } from "../../../../parameters" +import { garnet_constant } from "../../garnet-constructs/constants"; + +export interface GarnetIotprops { + dns_context_broker: string + vpc: Vpc +} + +export class GarnetIot extends Construct { + public readonly sqs_garnet_iot_url: string; + public readonly sns_garnet_iot: Topic; + public readonly iot_rule_sub_name: string + + constructor(scope: Construct, id: string, props: GarnetIotprops) { + super(scope, id); + + //CHECK PROPS + if (!props.vpc) { + throw new Error( + "The property vpc is required to create an instance of GarnetIot Construct" + ); + } + if (!props.dns_context_broker) { + throw new Error( + "The property dns_context_broker is required to create an instance of GarnetIot Construct" + ); + } + + // LAMBDA LAYER (SHARED LIBRARIES) + const layer_lambda_path = `./lib/layers`; + const layer_lambda = new LayerVersion(this, "LayerLambda", { + code: Code.fromAsset(layer_lambda_path), + compatibleRuntimes: [Runtime.NODEJS_18_X], + }) + + // SQS ENTRY POINT + const sqs_garnet_endpoint = new Queue(this, "SqsGarnetIot", { + queueName: `garnet-iot-queue-${Aws.REGION}`, + visibilityTimeout: Duration.seconds(55) + }) + this.sqs_garnet_iot_url = sqs_garnet_endpoint.queueUrl; + + // LAMBDA TO UPDATE DEVICE SHADOW + const lambda_update_shadow_path = `${__dirname}/lambda/updateShadow`; + const lambda_update_shadow = new Function(this, "LambdaUpdateShadow", { + functionName: `garnet-iot-update-shadow-lambda-${Names.uniqueId(this) + .slice(-4) + .toLowerCase()}`, + description: 'Garnet IoT - Function that updates shadows', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_update_shadow_path), + handler: "index.handler", + timeout: Duration.seconds(50), + logRetention: RetentionDays.THREE_MONTHS, + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix + }, + }); + + // ADD PERMISSION FOR LAMBDA THAT UPDATES SHADOW TO ACCESS SQS ENTRY POINT + lambda_update_shadow.addToRolePolicy( + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + resources: [`${sqs_garnet_endpoint.queueArn}`], + }) + ); + + // ADD PERMISSION TO ACCESS AWS IoT DEVICE SHADOW + lambda_update_shadow.addToRolePolicy( + new PolicyStatement({ + actions: ["iot:UpdateThingShadow"], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*/${garnet_constant.shadow_prefix}-*`, + ], + }) + ); + + // ADD THE SQS ENTRY POINT AS EVENT SOURCE FOR LAMBDA + lambda_update_shadow.addEventSource( + new SqsEventSource(sqs_garnet_endpoint, { batchSize: 10 }) + ); + + // SQS TO LAMBDA CONTEXT BROKER + const sqs_to_context_broker = new Queue(this, "SqsToLambdaContextBroker", { + queueName: `garnet-iot-contextbroker-queue-${Aws.REGION}`, + visibilityTimeout: Duration.seconds(55) + }); + + + // ROLE THAT GRANT ACCESS TO IOT RULE TO ACTIONS + const iot_rule_actions_role = new Role(this, "RoleGarnetIotRuleIngestion", { + assumedBy: new ServicePrincipal("iot.amazonaws.com"), + }); + iot_rule_actions_role.addToPolicy( + new PolicyStatement({ + resources: [ + `${sqs_to_context_broker.queueArn}` + ], + actions: [ + "sqs:SendMessage" + ], + }) + ) + + // IOT RULE THAT LISTENS TO CHANGES IN GARNET SHADOWS AND PUSH TO SQS + const iot_rule = new CfnTopicRule(this, "IoTRuleShadows", { + ruleName: `garnet_iot_rule_${Names.uniqueId(this).slice(-4).toLowerCase()}`, + topicRulePayload: { + awsIotSqlVersion: "2016-03-23", + ruleDisabled: false, + sql: `SELECT current.state.reported.* + FROM '$aws/things/+/shadow/name/+/update/documents' + WHERE startswith(topic(6), '${garnet_constant.shadow_prefix}') + AND NOT isUndefined(current.state.reported.type)`, + actions: [ + { + sqs: { + queueUrl: sqs_to_context_broker.queueUrl, + roleArn: iot_rule_actions_role.roleArn, + }, + } + ], + }, + }) + + + // LAMBDA THAT GETS MESSAGES FROM THE QUEUE AND UPDATES CONTEXT BROKER + const lambda_to_context_broker_path = `${__dirname}/lambda/updateContextBroker`; + const lambda_to_context_broker = new Function( + this, + "LambdaUpdateContextBroker", + { + functionName: `garnet-iot-update-broker-lambda-${Names.uniqueId(this) + .slice(-4) + .toLowerCase()}`, + description: 'Garnet IoT - Function that updates the context broker', + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }, + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_to_context_broker_path), + handler: "index.handler", + timeout: Duration.seconds(50), + logRetention: RetentionDays.THREE_MONTHS, + layers: [layer_lambda], + architecture: Architecture.ARM_64, + environment: { + DNS_CONTEXT_BROKER: props.dns_context_broker, + URL_SMART_DATA_MODEL: Parameters.smart_data_model_url, + AWSIOTREGION: Aws.REGION, + SHADOW_PREFIX: garnet_constant.shadow_prefix + }, + } + ); + + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses", + ], + resources: ["*"], + }) + ); + + // ADD PERMISSION FOR LAMBDA TO ACCESS SQS + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + resources: [`${sqs_to_context_broker.queueArn}`], + }) + ); + + lambda_to_context_broker.addToRolePolicy( + new PolicyStatement({ + actions: ["iot:UpdateThingShadow", "iot:GetThingShadow"], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:thing/*/${garnet_constant.shadow_prefix}-*`, + ], + }) + ); + + lambda_to_context_broker.addEventSource( + new SqsEventSource(sqs_to_context_broker, { batchSize: 10 }) + ); + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateContextBroker/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateContextBroker/index.js new file mode 100644 index 0000000..b929b16 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateContextBroker/index.js @@ -0,0 +1,94 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX +const dns_broker = `http://${process.env.DNS_CONTEXT_BROKER}/ngsi-ld/v1` +const URL_SMART_DATA_MODEL = process.env.URL_SMART_DATA_MODEL + +const {IoTDataPlaneClient, UpdateThingShadowCommand, GetThingShadowCommand} = require('@aws-sdk/client-iot-data-plane') +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +const axios = require('axios') + + + + +exports.handler = async (event, context) => { + + try { + let entities = [] + + for await (let msg of event.Records){ + let payload = JSON.parse(msg.body) + const thingName = `${payload.id.split(':').slice(-1)}` + if(!payload.id || !payload.type){ + throw new Error('Invalid entity: id or type is missing') + } + + // CHECK IF LOCATION IS IN THE PAYLOAD. IF NOT GET IT FROM THING SHADOW. + if(!payload.location && payload.type != 'Thing') { + + try { + let {payload : device_shadow} = await iotdata.send( + new GetThingShadowCommand({ + thingName: thingName, + shadowName: `${shadow_prefix}-Thing` + }) + ) + + device_shadow = JSON.parse( + new TextDecoder('utf-8').decode(device_shadow) + ) + payload.location = device_shadow.state.reported.location + + if(payload.location){ + const shadow_payload = { + state: { + reported: payload + } + } + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({ + payload: JSON.stringify(shadow_payload), + thingName: thingName, + shadowName: `${shadow_prefix}-${payload.type}` + }) + ) + } + + + + } catch (e) { + console.log(e.message) + } + } + if (payload.raw) delete payload.raw + if (payload['@context']) { + console.log(`Removed the context:`) + console.log(payload['@context']) + delete payload['@context'] + } + entities.push(payload) + } + const headers = { + 'Content-Type': 'application/json', + 'Link': `<${URL_SMART_DATA_MODEL}>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"` + } + try { + let upsert = await axios.post(`${dns_broker}/entityOperations/upsert`, entities, {headers: headers}) + } catch (e) { + log_error(event,context, e.message, e) + } + } catch (e) { + log_error(event,context, e.message, e) + } +} + + +const log_error = (event, context, message, error) => { + console.error(JSON.stringify({ + message: message, + event: event, + error: error, + context: context + })) +} + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateShadow/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateShadow/index.js new file mode 100644 index 0000000..62565d9 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-ingestion/lambda/updateShadow/index.js @@ -0,0 +1,86 @@ +const iot_region = process.env.AWSIOTREGION +const shadow_prefix = process.env.SHADOW_PREFIX +const url_broker = process.env.URL_CONTEXT_BROKER + +const {IoTDataPlaneClient, UpdateThingShadowCommand} = require('@aws-sdk/client-iot-data-plane') +const iotdata = new IoTDataPlaneClient({region: iot_region}) + + +exports.handler = async (event, context) => { + try { + + for await (let msg of event.Records){ + let payload = JSON.parse(msg.body) + + if(!payload.id || !payload.type){ + throw new Error('Invalid entity - id or type is missing') + } + + if (payload['@context']) { + console.log(`Removed the context:`) + console.log(payload['@context']) + delete payload['@context'] + } + const thingName = `${payload.id.split(':').slice(-1)}` + + for (let [key, value] of Object.entries(payload)) { + if(!["type", "id", "@context"].includes(key)) { + + if( typeof value == "object" && !Array.isArray(value)){ + recursive_concise(key, value) + } else { + payload[key] = { + "value": value + } + } + + + } + } + + try { + const shadow_payload = { + state: { + reported: payload + } + } + + let updateThingShadow = await iotdata.send( + new UpdateThingShadowCommand({ + payload: JSON.stringify(shadow_payload), + thingName: thingName, + shadowName: `${shadow_prefix}-${payload.type}` + }) + ) + + } catch (e) { + log_error(event,context, e.message, e) + } + + } + + } catch (e) { + log_error(event,context, e.message, e) + } +} + +const recursive_concise = (key, value) => { + + if( typeof value == 'object' && !Array.isArray(value)){ + if(['Property', 'Relationship', 'GeoProperty'].includes(value["type"])) { + delete value["type"] + } + for (let [skey, svalue] of Object.entries(value)){ + recursive_concise(skey,svalue) + } + } +} + +const log_error = (event, context, message, error) => { + console.error(JSON.stringify({ + message: message, + event: event, + error: error, + context: context + })) +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/index.ts new file mode 100644 index 0000000..3faa42c --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/index.ts @@ -0,0 +1,357 @@ +import { Aws, CustomResource, Duration, Lazy, Names, RemovalPolicy } from "aws-cdk-lib" +import { InterfaceVpcEndpoint, Peer, Port, SecurityGroup, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2" +import { Policy, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam" +import { CfnAuthorizer, CfnDomainConfiguration, CfnTopicRule } from "aws-cdk-lib/aws-iot" +import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose" +import { Runtime, Function, Code, Architecture, CfnPermission, LayerVersion } from "aws-cdk-lib/aws-lambda" +import { ARecord, PrivateHostedZone, RecordTarget } from "aws-cdk-lib/aws-route53" +import { InterfaceVpcEndpointTarget } from "aws-cdk-lib/aws-route53-targets" +import { Bucket } from "aws-cdk-lib/aws-s3" +import { Construct } from "constructs" +import { Parameters } from "../../../../parameters" +import { Provider } from "aws-cdk-lib/custom-resources" +import { garnet_constant } from "../../garnet-constructs/constants" + +export interface GarnetLakeProps { + vpc: Vpc, + bucket_name: string + dns_context_broker: string, + az1: string + az2: string + } + + +export class GarnetLake extends Construct { + + public readonly iot_rule_lake_name: string + public readonly iot_domain_name: string + + constructor(scope: Construct, id: string, props: GarnetLakeProps) { + super(scope, id) + + + // KINESIS FIREHOSE TO DATALAKE BUCKET + + // DATALAKE BUCKET + const bucket = Bucket.fromBucketName(this, "GarnetBucket", props.bucket_name) + + + // ROLE THAT GRANTS ACCESS TO FIREHOSE TO READ/WRITE BUCKET + const role_firehose = new Role(this, "FirehoseRole", { + assumedBy: new ServicePrincipal("firehose.amazonaws.com"), + }); + bucket.grantReadWrite(role_firehose) + + + // LAMBDA THAT EXTRACT ENTITIES FROM SUBSCRIPTION IN FIREHOSE STREAM + const lambda_transform_path = `${__dirname}/lambda/transform` + const lambda_transform = new Function(this, 'LakeTransformLambda', { + functionName: `garnet-lake-transform-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Lake - Function that transforms the Kinesis Firehose records to extract entities from notifications', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_transform_path), + handler: 'index.handler', + timeout: Duration.minutes(1), + architecture: Architecture.ARM_64 + }) + + + // KINESIS FIREHOSE DELIVERY STREAM + const kinesis_firehose = new CfnDeliveryStream( this, "GarnetFirehose", { + deliveryStreamName: `garnet-lake-firehose-stream-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + deliveryStreamType: "DirectPut", + extendedS3DestinationConfiguration: { + bucketArn: bucket.bucketArn, + roleArn: role_firehose.roleArn, + bufferingHints: { + intervalInSeconds: 60, + sizeInMBs: 64, + }, + processingConfiguration: { + enabled: true, + processors: [ + { + type: "RecordDeAggregation", + parameters: [ + { + parameterName: "SubRecordType", + parameterValue: "JSON", + } + ] + }, + { + type: 'Lambda', + parameters: [{ + parameterName: 'LambdaArn', + parameterValue: lambda_transform.functionArn + }] + }, + { + type: "MetadataExtraction", + parameters: [ + { + parameterName: "MetadataExtractionQuery", + parameterValue: "{type:.type}", + }, + { + parameterName: "JsonParsingEngine", + parameterValue: "JQ-1.6", + }, + ], + } + ], + }, + dynamicPartitioningConfiguration: { + enabled: true, + }, + prefix: `type=!{partitionKeyFromQuery:type}/dt=!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + errorOutputPrefix: `type=!{firehose:error-output-type}/dt=!{timestamp:yyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + }, + } + ) + + lambda_transform.grantInvoke(role_firehose) + + // IOT RULE THAT LISTENS TO SUBSCRIPTIONS AND PUSH TO FIREHOSE + const iot_rule_lake_name = `garnet_lake_rule_${Names.uniqueId(this).slice(-4).toLowerCase()}` + this.iot_rule_lake_name = iot_rule_lake_name + + const iot_rule_lake_role = new Role(this, "RoleGarnetLakeIotRuleIngestion", { + assumedBy: new ServicePrincipal("iot.amazonaws.com"), + }) + + const iot_rule_lake_policy = new Policy(this, 'PolicyGarnetIotRuleIngestion', { + statements: [ + new PolicyStatement({ + resources: [ `${kinesis_firehose.attrArn}` ], + actions: [ + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams", + "firehose:ListTagsForDeliveryStream", + "firehose:PutRecord", + "firehose:PutRecordBatch", + ] + }) + ] + }) + + iot_rule_lake_role.attachInlinePolicy(iot_rule_lake_policy) + + + const iot_rule_sub = new CfnTopicRule(this, "IotRuleSub", { + ruleName: iot_rule_lake_name, + topicRulePayload: { + awsIotSqlVersion: "2016-03-23", + ruleDisabled: false, + sql: `SELECT VALUE data FROM 'garnetsubdatalake'`, + actions: [ + { + firehose: { + deliveryStreamName: kinesis_firehose.ref, + roleArn: iot_rule_lake_role.roleArn, + separator: "\n", + batchMode: true + }, + }, + ], + }, + }) + + + // SECURITY GROUP + const sg_garnet_vpc_endpoint = new SecurityGroup(this, 'LakeSecurityGroup', { + securityGroupName: `garnet-lake-endpoint-sg-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + vpc: props.vpc, + allowAllOutbound: true + }) + + sg_garnet_vpc_endpoint.addIngressRule(Peer.anyIpv4(), Port.tcp(443)) + + // VPC ENDPOINT + const vpc_endpoint = new InterfaceVpcEndpoint(this, 'GarnetLakeIoTEndpoint', { + vpc: props.vpc, + subnets: { + availabilityZones: [props.az1, props.az2] + }, + service: { + name: `com.amazonaws.${Aws.REGION}.iot.data`, + port: 443 + }, + privateDnsEnabled: false, + securityGroups: [sg_garnet_vpc_endpoint] + }) + + // LAMBDA FUNCTION FOR AUTHORIZER + + const lambda_garnet_authorizer_path = `${__dirname}/lambda/authorizer` + const lambda_garnet_authorizer = new Function(this, 'LakeAuthorizerLambda', { + functionName: `garnet-iot-authorizer-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Lake - Function for the AWS IoT Authorizer', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_garnet_authorizer_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + VPC_ENDPOINT: vpc_endpoint.vpcEndpointId, + LAKERULENAME: iot_rule_lake_name, + } + }) + + + // AWS IoT AUTHORIZER + + const iot_authorizer = new CfnAuthorizer(this, 'IotAuthorizer', { + authorizerFunctionArn: lambda_garnet_authorizer.functionArn, + authorizerName: `garnet-iot-authorizer-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + status: "ACTIVE", + signingDisabled: true, + enableCachingForHttp: true + }) + + new CfnPermission(this, 'AuthorizerLambdaPermission', { + principal: `iot.amazonaws.com`, + action: 'lambda:InvokeFunction', + functionName: lambda_garnet_authorizer.functionName, + sourceArn: `${iot_authorizer.attrArn}` + }) + + + const lambda_custom_iot_domain_path = `${__dirname}/lambda/domain` + const lambda_custom_iot_domain = new Function(this, 'IoTCustomDomainLambda', { + functionName: `garnet-custom-iot-domain-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Lake - Function that updates or creates the IoT custom domain for the IoT Rule Data Lake', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_custom_iot_domain_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + DOMAIN_NAME: garnet_constant.iotDomainName, + AUTHORIZER_NAME: Lazy.string( { produce(): string { return iot_authorizer.authorizerName! } }) + } + }) + + lambda_custom_iot_domain.addToRolePolicy(new PolicyStatement({ + actions: [ + "iot:CreateDomainConfiguration", + "iot:DescribeDomainConfiguration", + "iot:UpdateDomainConfiguration" + ], + resources: ['*'] + })) + + const custom_iot_domain_provider = new Provider(this, 'CustomIotDomainProvider', { + onEventHandler: lambda_custom_iot_domain, + providerFunctionName: `garnet-provider-iot-domain-${Names.uniqueId(this).slice(-4).toLowerCase()}` + }) + + const custom_iot_domain_resource = new CustomResource(this, 'CustomIotDomainResource', { + serviceToken: custom_iot_domain_provider.serviceToken + }) + + this.iot_domain_name = custom_iot_domain_resource.getAtt('domainName').toString() + + custom_iot_domain_resource.node.addDependency(iot_authorizer) + lambda_custom_iot_domain.node.addDependency(iot_authorizer) + + + + // ROUTE 53 HOSTED ZONE + const lake_hosted_zone = new PrivateHostedZone(this, 'LakeHostedZone', { + zoneName: custom_iot_domain_resource.getAtt('domainName').toString(), + vpc: props.vpc + }) + + lake_hosted_zone.node.addDependency(custom_iot_domain_resource) + + // ROUTE 53 RECORD + const lake_record = new ARecord(this, 'LakeARecord', { + zone: lake_hosted_zone, + target: RecordTarget.fromAlias(new InterfaceVpcEndpointTarget(vpc_endpoint)) + }) + + lake_record.node.addDependency(lake_hosted_zone) + + // LAMBDA LAYER (SHARED LIBRARIES) + const layer_lambda_path = `./lib/layers`; + const layer_lambda = new LayerVersion(this, "LayerLambda", { + code: Code.fromAsset(layer_lambda_path), + compatibleRuntimes: [Runtime.NODEJS_18_X], + }) + + // LAMBDA THAT CREATE A SUBSCRIPTION IN THE BROKER TO SUBSCRIBE TO ALL ENTITIES + const lambda_garnet_all_sub_path = `${__dirname}/lambda/suball` + const lambda_garnet_all_sub = new Function(this, 'GarnetSubAllFunction', { + functionName: `garnet-sub-all-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Lake - Function that subscribe to all types in the broker to feed the Garnet Lake', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_garnet_all_sub_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + layers: [layer_lambda], + architecture: Architecture.ARM_64, + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }, + environment: { + DNS_CONTEXT_BROKER: props.dns_context_broker, + LAKERULENAME: iot_rule_lake_name, + URL_SMART_DATA_MODEL: Parameters.smart_data_model_url, + IOTDOMAINNAME: custom_iot_domain_resource.getAtt('domainName').toString(), + SUBNAME: garnet_constant.subAllName + } + }) + + const bucket_provider = new Provider(this, 'CustomSubAllProvider', { + onEventHandler: lambda_garnet_all_sub, + providerFunctionName: `garnet-provider-sub-all-${Names.uniqueId(this).slice(-4).toLowerCase()}` + }) + + // new CustomResource(this, 'CustomSubAllResource', { + // serviceToken: bucket_provider.serviceToken + // }) + + + // CUSTOM RESOURCE WITH A LAMBDA THAT WILL CREATE ATHENA WORKGROUP AND GLUE DB + const lambda_athena_path = `${__dirname}/lambda/athena` + const lambda_athena = new Function(this, 'AthenaFunction', { + functionName: `garnet-lake-athena-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Lake - Function that creates Athena resources', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_athena_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + BUCKET_NAME: Parameters.garnet_bucket, + CATALOG_ID: Aws.ACCOUNT_ID, + GLUEDB_NAME: garnet_constant.gluedbName + } + }) + + lambda_athena.addToRolePolicy(new PolicyStatement({ + actions: [ + "athena:CreateWorkGroup", + "glue:CreateDatabase" + ], + resources: ["*"] + })) + + const athena_provider = new Provider(this, 'AthenaProvider', { + onEventHandler: lambda_athena, + providerFunctionName: `garnet-provider-custom-athena-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + }) + + const athena_resource = new CustomResource(this, 'CustomBucketAthenaResource', { + serviceToken: athena_provider.serviceToken, + + }) + + + } + + + +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/athena/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/athena/index.js new file mode 100644 index 0000000..3e7eb2a --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/athena/index.js @@ -0,0 +1,44 @@ +const { AthenaClient, CreateWorkGroupCommand } = require("@aws-sdk/client-athena") +const { GlueClient, CreateDatabaseCommand } = require("@aws-sdk/client-glue") +const athena = new AthenaClient() +const glue = new GlueClient() +const BUCKET_NAME = process.env.BUCKET_NAME +const CATALOG_ID = process.env.CATALOG_ID +const GLUEDB_NAME = process.env.GLUEDB_NAME + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='create' || request_type == 'update') { + + try { + await athena.send( + new CreateWorkGroupCommand({ + Name: 'garnet', + Configuration: { + ResultConfiguration: { + OutputLocation: `s3://${BUCKET_NAME}-athena-results/` + } + } + }) + ) + } catch (e) { + console.log(e.message) + } + + try { + await glue.send( + new CreateDatabaseCommand({ + CatalogId: CATALOG_ID, + DatabaseInput: { + Name: GLUEDB_NAME + } + }) + ) + } catch (e) { + console.log(e.message) + } + + return true + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/authorizer/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/authorizer/index.js new file mode 100644 index 0000000..f72753c --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/authorizer/index.js @@ -0,0 +1,29 @@ +const VPC_ENDPOINT = process.env.VPC_ENDPOINT +const LAKERULENAME = process.env.LAKERULENAME + +exports.handler = async (event) => { + + let auth = {} + auth.isAuthenticated = true + auth.principalId = "garnetDatalakePrincipal" + let policyDocument = {} + policyDocument.Version = "2012-10-17" + policyDocument.Statement = [] + let publishStatement = {} + publishStatement.Action = ["iot:Publish"] + publishStatement.Effect = "Allow" + publishStatement.Resource = [`arn:aws:iot:*:*:topic/$aws/rules/${LAKERULENAME}`] + publishStatement.Condition = {} + publishStatement.Condition.StringEquals = {} + publishStatement.Condition.StringEquals["aws:SourceVpce"] = VPC_ENDPOINT + + policyDocument.Statement[0] = publishStatement + + auth.policyDocuments = [policyDocument] + auth.disconnectAfterInSeconds = 86400 + auth.refreshAfterInSeconds = 86400 + + return auth + + +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/domain/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/domain/index.js new file mode 100644 index 0000000..d2903e6 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/domain/index.js @@ -0,0 +1,65 @@ +const { IoTClient, CreateDomainConfigurationCommand, DescribeDomainConfigurationCommand, UpdateDomainConfigurationCommand } = require("@aws-sdk/client-iot") +const iot = new IoTClient({}) +const DOMAIN_NAME = process.env.DOMAIN_NAME +const AUTHORIZER_NAME = process.env.AUTHORIZER_NAME + +exports.handler = async (event) => { + console.log(event) + let request_type = event['RequestType'].toLowerCase() + if (request_type=='create' || request_type == 'update') { + + try { + try { + await iot.send( + new UpdateDomainConfigurationCommand({ + domainConfigurationName: DOMAIN_NAME, + domainConfigurationStatus: 'ENABLED', + authorizerConfig: { + defaultAuthorizerName: `${AUTHORIZER_NAME}`, + allowAuthorizerOverride: false + } + }) + ) + } catch (e) { + console.log(e.message) + if(e.name == 'ResourceNotFoundException'){ + + await iot.send( + new CreateDomainConfigurationCommand({ + domainConfigurationName: DOMAIN_NAME, + domainConfigurationStatus: 'ENABLED', + authorizerConfig: { + defaultAuthorizerName: `${AUTHORIZER_NAME}`, + allowAuthorizerOverride: false + } + }) + ) + + } + + } + + const {domainName, domainConfigurationArn, domainConfigurationName, domainConfigurationStatus} = await iot.send( + new DescribeDomainConfigurationCommand({ + domainConfigurationName: DOMAIN_NAME + }) + ) + + console.log({domainName}) + + return { + Data: { + domainName: domainName, + } + } + + } catch (e) { + console.log(e) + + return null + } + + + + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/suball/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/suball/index.js new file mode 100644 index 0000000..86fbfda --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/suball/index.js @@ -0,0 +1,51 @@ +const dns_broker = `http://${process.env.DNS_CONTEXT_BROKER}/ngsi-ld/v1`; +const URL_SMART_DATA_MODEL = process.env.URL_SMART_DATA_MODEL; +const IOTDOMAINNAME = process.env.IOTDOMAINNAME; +const LAKERULENAME = process.env.LAKERULENAME; +const SUBNAME = process.env.SUBNAME; +const axios = require("axios"); + +exports.handler = async (event, context) => { + console.log(IOTDOMAINNAME); + console.log(LAKERULENAME); + console.log(SUBNAME); + + let request_type = event['RequestType'].toLowerCase() + if (request_type=='create' || request_type == 'update') { + try { + let allsub = { + id: `urn:ngsi-ld:Subscription:${SUBNAME}`, + description: "GARNET DATALAKE SUB - DO NOT DELETE", + type: "Subscription", + entities: [{ type: "*" }], + notification: { + format: "concise", + endpoint: { + uri: `https://${IOTDOMAINNAME}/topics/$aws/rules/${LAKERULENAME}?qos=1`, + accept: "application/json", + }, + }, + } + const headers = { + 'Content-Type': 'application/json', + 'Link': `<${URL_SMART_DATA_MODEL}>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"` + } + let addsub = await axios.post(`${dns_broker}/subscriptions`, allsub, { + headers: headers, + }) + } catch (e) { + log_error(event, context, e.message, e); + } + } +}; + +const log_error = (event, context, message, error) => { + console.error( + JSON.stringify({ + message: message, + event: event, + error: error, + context: context, + }) + ); +}; diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/transform/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/transform/index.js new file mode 100644 index 0000000..4ce59a3 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-lake/lambda/transform/index.js @@ -0,0 +1,45 @@ +exports.handler = async (event, context) => { + + const output = event.records.map((record) => ({ + /* This transformation is the "identity" transformation, the data is left intact */ + recordId: record.recordId, + result: 'Ok', + data: Buffer.from(JSON.stringify(transform(JSON.parse(Buffer.from(record.data, 'base64').toString('utf8')))), 'utf-8').toString('base64') + })) + return { records: output } +} + +const recursive_concise = (key, value) => { + + if( typeof value == 'object' && !Array.isArray(value)){ + if(['Property', 'Relationship', 'GeoProperty'].includes(value["type"])) { + delete value["type"] + } + for (let [skey, svalue] of Object.entries(value)){ + recursive_concise(skey,svalue) + } + } +} + +const transform = (payload) => { + if(!payload?.type) return + if(payload?.type?.includes('#')){ + payload.type = `${payload.type.split('#').slice(-1)}` + } + if(payload?.type?.includes('/')){ + payload.type = `${payload.type.split('/').slice(-1)}` + } + for (let [key, value] of Object.entries(payload)) { + if(!["type", "id", "@context"].includes(key)) { + if( typeof value == "object" && !Array.isArray(value)){ + recursive_concise(key, value) + } else { + payload[key] = { + "value": value + } + } + } + } + console.log(payload) + return payload +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/index.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/index.ts new file mode 100644 index 0000000..95abc5d --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/index.ts @@ -0,0 +1,200 @@ +import { Aws, CfnOutput, Duration, Names } from "aws-cdk-lib" +import { EndpointType, LambdaRestApi } from "aws-cdk-lib/aws-apigateway" +import { InterfaceVpcEndpoint, Peer, Port, SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2" +import { AnyPrincipal, Effect, Policy, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam" +import { Architecture, Code, Function, Runtime } from "aws-cdk-lib/aws-lambda" +import { Construct } from "constructs" +import { garnet_constant } from "../../garnet-constructs/constants" +import { CfnTopicRule } from "aws-cdk-lib/aws-iot" +import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose" +import { Bucket } from "aws-cdk-lib/aws-s3" + +export interface GarnetPrivateSubProps { + vpc: Vpc, + bucket_name: string + } + + export class GarnetPrivateSub extends Construct { + + public readonly private_sub_endpoint: string + + constructor(scope: Construct, id: string, props: GarnetPrivateSubProps) { + super(scope, id) + + // SECURITY GROUP + const sg_garnet_vpc_endpoint = new SecurityGroup(this, 'PrivateSubSecurityGroup', { + securityGroupName: `garnet-private-sub-endpoint-sg-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + vpc: props.vpc, + allowAllOutbound: true + }) + sg_garnet_vpc_endpoint.addIngressRule(Peer.anyIpv4(), Port.tcp(443)) + + // VPC ENDPOINT + const vpc_endpoint = new InterfaceVpcEndpoint(this, 'GarnetPrivateSubEndpoint', { + vpc: props.vpc, + service: { + name: `com.amazonaws.${Aws.REGION}.execute-api`, + port: 443 + }, + privateDnsEnabled: true, + securityGroups: [sg_garnet_vpc_endpoint] + }) + + // LAMBDA + const lambda_garnet_private_sub_path = `${__dirname}/lambda/garnetSub` + const lambda_garnet_private_sub = new Function(this, 'GarnetSubFunction', { + functionName: `garnet-private-sub-lambda-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + description: 'Garnet Private Sub - Function for the private subscription', + runtime: Runtime.NODEJS_18_X, + code: Code.fromAsset(lambda_garnet_private_sub_path), + handler: 'index.handler', + timeout: Duration.seconds(50), + architecture: Architecture.ARM_64, + environment: { + AWSIOTREGION: Aws.REGION + } + }) + + lambda_garnet_private_sub.addToRolePolicy(new PolicyStatement({ + actions: ["iot:Publish"], + resources: [ + `arn:aws:iot:${Aws.REGION}:${Aws.ACCOUNT_ID}:topic/garnet/subscriptions/*`, + ] + })) + + // POLICY FOR API + const api_policy = new PolicyDocument({ + statements: [ + new PolicyStatement({ + principals: [new AnyPrincipal], + actions: ['execute-api:Invoke'], + resources: ['execute-api:/*'], + effect: Effect.DENY, + conditions: { + StringNotEquals: { + "aws:SourceVpce": vpc_endpoint.vpcEndpointId + } + } + }), + new PolicyStatement({ + principals: [new AnyPrincipal], + actions: ['execute-api:Invoke'], + resources: ['execute-api:/*'], + effect: Effect.ALLOW + }) + ] + }) + + const api_private_sub = new LambdaRestApi(this, 'ApiPrivateSub', { + restApiName:'garnet-private-sub-endpoint-api', + endpointTypes: [EndpointType.PRIVATE], + handler: lambda_garnet_private_sub, + policy: api_policy, + description: "Garnet Private Endpoint for Subscriptions", + deployOptions: { + stageName: "privatesub" + } + }) + + this.private_sub_endpoint = api_private_sub.url + + new CfnOutput(this, 'ApiEndpoint', { + value: api_private_sub.url, + description: 'Private API Endpoint for Subscriptions' + }) + + + // KINESIS FIREHOSE TO DATALAKE BUCKET + + // DATALAKE BUCKET + const bucket = Bucket.fromBucketName(this, "GarnetBucket", props.bucket_name) + + + // ROLE THAT GRANTS ACCESS TO FIREHOSE TO READ/WRITE BUCKET + const role_firehose = new Role(this, "FirehoseRole", { + assumedBy: new ServicePrincipal("firehose.amazonaws.com"), + }); + bucket.grantReadWrite(role_firehose) + + // KINESIS FIREHOSE DELIVERY STREAM + const kinesis_firehose = new CfnDeliveryStream( this, "GarnetFirehose", { + deliveryStreamName: `garnet-sub-firehose-stream-${Names.uniqueId(this).slice(-4).toLowerCase()}`, + deliveryStreamType: "DirectPut", + extendedS3DestinationConfiguration: { + bucketArn: bucket.bucketArn, + roleArn: role_firehose.roleArn, + bufferingHints: { + intervalInSeconds: 60, + sizeInMBs: 64, + }, + processingConfiguration: { + enabled: true, + processors: [ + { + type: "MetadataExtraction", + parameters: [ + { + parameterName: "MetadataExtractionQuery", + parameterValue: "{type:.type}", + }, + { + parameterName: "JsonParsingEngine", + parameterValue: "JQ-1.6", + }, + ], + }, + ], + }, + dynamicPartitioningConfiguration: { + enabled: true, + }, + prefix: `type=!{partitionKeyFromQuery:type}/dt=!{timestamp:yyyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + errorOutputPrefix: `type=!{firehose:error-output-type}/dt=!{timestamp:yyy}-!{timestamp:MM}-!{timestamp:dd}-!{timestamp:HH}/`, + }, + } + ) + + // IOT RULE THAT LISTENS TO SUBSCRIPTIONS AND PUSH TO FIREHOSE + const iot_rule_sub_name = `garnet_subscriptions_rule_${Names.uniqueId(this).slice(-4).toLowerCase()}` + + const iot_rule_sub_role = new Role(this, "RoleGarnetIotRuleIngestion", { + assumedBy: new ServicePrincipal("iot.amazonaws.com"), + }) + + const iot_rule_sub_policy = new Policy(this, 'PolicyGarnetIotRuleIngestion', { + statements: [ + new PolicyStatement({ + resources: [ `${kinesis_firehose.attrArn}` ], + actions: [ + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams", + "firehose:ListTagsForDeliveryStream", + "firehose:PutRecord", + "firehose:PutRecordBatch", + ] + }) + ] + }) + + iot_rule_sub_role.attachInlinePolicy(iot_rule_sub_policy) + + const iot_rule_sub = new CfnTopicRule(this, "IotRuleSub", { + ruleName: iot_rule_sub_name, + topicRulePayload: { + awsIotSqlVersion: "2016-03-23", + ruleDisabled: false, + sql: `SELECT * FROM 'garnet/subscriptions/+'`, + actions: [ + { + firehose: { + deliveryStreamName: kinesis_firehose.ref, + roleArn: iot_rule_sub_role.roleArn, + separator: "\n", + }, + }, + ], + }, + }) + + } + } \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/lambda/garnetSub/index.js b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/lambda/garnetSub/index.js new file mode 100644 index 0000000..95dcd4f --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-private-sub/lambda/garnetSub/index.js @@ -0,0 +1,89 @@ +const iot_region = process.env.AWSIOTREGION +const sub_all = process.env.SUBALL +const { IoTDataPlaneClient, PublishCommand } = require("@aws-sdk/client-iot-data-plane") +const iotdata = new IoTDataPlaneClient({region: iot_region}) + +exports.handler = async (event) => { + try { + const {body} = event + if(!body){ + return { + statusCode: 400, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: 'Bad Request. Notification is the only type valid'}) + } + } + const payload = JSON.parse(body) + + if(payload?.type != "Notification") { + console.log('ERROR not Notification') + return { + statusCode: 400, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: 'Bad Request. Notification is the only type valid'}) + } + } + // GET THE SUBSCRIPTION NAME FROM SUBSCRIPTION ID + const subName = `${payload.subscriptionId.split(':').slice(-1)}` + + payload.data.forEach(entity => { + for (let [key, value] of Object.entries(entity)) { + if(!['type', 'id', '@context'].includes(key)) { + + if( typeof value == 'object' && !Array.isArray(value)){ + recursive_concise(key, value) + } else { + entity[key] = { + value: value + } + } + + + } + } + }) + + const publish = await iotdata.send( + new PublishCommand({ + topic: `garnet/subscriptions/${subName}`, + payload: JSON.stringify(payload) + }) + ) + + + const response = { + statusCode: 200 + } + return response + + } catch (e) { + const response = { + statusCode: 500, + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({message: e.message}), + } + console.log(e) + return response + + } + + +} + +const recursive_concise = (key, value) => { + + if( typeof value == 'object' && !Array.isArray(value)){ + if(['Property', 'Relationship', 'GeoProperty'].includes(value['type'])) { + delete value['type'] + } + for (let [skey, svalue] of Object.entries(value)){ + recursive_concise(skey,svalue) + } + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-stack.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-stack.ts new file mode 100644 index 0000000..4a280fc --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/garnet-iot-stack.ts @@ -0,0 +1,59 @@ +import { NestedStack, NestedStackProps } from 'aws-cdk-lib' +import { Vpc } from 'aws-cdk-lib/aws-ec2' +import { Construct } from 'constructs' +import { GarnetIotApi } from './garnet-iot-api' +import { GarnetIot } from './garnet-iot-ingestion' +import { GarnetPrivateSub } from './garnet-iot-private-sub' +import { GarnetLake } from './garnet-iot-lake' + +export interface GarnetIotStackProps extends NestedStackProps { + dns_context_broker: string, + vpc: Vpc, + api_ref: string, + bucket_name: string, + az1: string, + az2: string +} + +export class GarnetIotStack extends NestedStack { + + public readonly iot_sqs_endpoint_url : string + public readonly private_sub_endpoint: string + + constructor(scope: Construct, id: string, props: GarnetIotStackProps) { + super(scope, id, props) + + // GARNET IoT CORE + const garnet_iot_core_construct = new GarnetIot(this, 'Core', { + vpc: props.vpc, + dns_context_broker: props.dns_context_broker + }) + + // GARNET IOT API + const garnet_iot_api_construct= new GarnetIotApi(this, 'Api', { + api_ref: props.api_ref, + vpc: props.vpc, + dns_context_broker: props.dns_context_broker + }) + + // GARNET IOT PRIVATE SUB + const garnet_private_sub_construct = new GarnetPrivateSub(this, "PrivateSub", { + vpc: props.vpc, + bucket_name: props.bucket_name + }) + + + // GARNET IOT DATA LAKE + const lake_construct = new GarnetLake(this, 'Lake', { + vpc: props.vpc, + bucket_name: props.bucket_name, + dns_context_broker: props.dns_context_broker, + az1: props.az1, + az2: props.az2 + }) + + + this.iot_sqs_endpoint_url = garnet_iot_core_construct.sqs_garnet_iot_url + this.private_sub_endpoint = garnet_private_sub_construct.private_sub_endpoint + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package-lock.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package-lock.json new file mode 100644 index 0000000..125f2a8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package-lock.json @@ -0,0 +1,435 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nodejs", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1426.0", + "axios": "^1.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1426.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1426.0.tgz", + "integrity": "sha512-qq4ydcRzQW2IqjMdCz5FklORREEtkSCJ2tm9CUJ2PaUOaljxpdxq9UI64vXiyRD+GIp5vdkmVNoTRi2rCXh3rA==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + } + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package.json new file mode 100644 index 0000000..b6289e8 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-iot/layers/nodejs/package.json @@ -0,0 +1,16 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1426.0", + "axios": "^1.4.0" + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-orion/garnet-orion.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-orion/garnet-orion.ts new file mode 100644 index 0000000..2e0aa40 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-orion/garnet-orion.ts @@ -0,0 +1,42 @@ +import { Aws, CfnOutput, NestedStack, NestedStackProps } from "aws-cdk-lib" +import { Construct } from "constructs" +import { GarnetSecret } from "../garnet-constructs/secret"; +import { GarnetNetworking } from "../garnet-constructs/networking"; +//import { GarnetOrionDatabase } from "./database"; +//import { GarnetOrionFargate } from "./fargate"; +import { Parameters } from "../../../parameters"; +import { Vpc } from "aws-cdk-lib/aws-ec2"; +import { GarnetApiGateway } from "../garnet-constructs/apigateway"; +import { Secret } from "aws-cdk-lib/aws-secretsmanager"; + +export interface GarnetOrionProps extends NestedStackProps{ + vpc: Vpc, + secret: Secret +} + +export class GarnetOrion extends NestedStack { + + public readonly dns_context_broker: string + public readonly broker_api_endpoint: string + public readonly api_ref: string + + + constructor(scope: Construct, id: string, props: GarnetOrionProps) { + super(scope, id, props) + + const api_stack = new GarnetApiGateway(this, "Api", { + vpc: props.vpc, + fargate_albListenerArn: Parameters.amazon_eks_cluster_load_balancer_listener_arn, //TODO Replace with EKS Cluster Load Balancer Listener ARN // Pass the listenerArn as a string fargate_alb: ``//fargate_construct.fargate_alb, + }) + + new CfnOutput(this, "garnet_endpoint", { + value: `https://${api_stack.api_ref}.execute-api.${Aws.REGION}.amazonaws.com`, + }) + + this.broker_api_endpoint = `https://${api_stack.api_ref}.execute-api.${Aws.REGION}.amazonaws.com`; + this.dns_context_broker = Parameters.amazon_eks_cluster_load_balancer_dns //TODO Replace with EKS Cluster Load Balancer DNS name //fargate_construct.fargate_alb.loadBalancer.loadBalancerDnsName; + this.api_ref = api_stack.api_ref; + + } + +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-scorpio/garnet-scorpio.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-scorpio/garnet-scorpio.ts new file mode 100644 index 0000000..19973cf --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/lib/stacks/garnet-scorpio/garnet-scorpio.ts @@ -0,0 +1,66 @@ +import { Aws, CfnOutput, NestedStack, NestedStackProps } from "aws-cdk-lib"; +import { Vpc } from "aws-cdk-lib/aws-ec2"; +import { Construct } from "constructs"; +import { Parameters } from "../../../parameters"; +import { GarnetApiGateway } from "../garnet-constructs/apigateway"; +//import { GarnetScorpioDatabase } from "./database"; +//import { GarnetScorpioFargate } from "./fargate"; +import { GarnetNetworking } from "../garnet-constructs/networking"; +import { GarnetSecret } from "../garnet-constructs/secret"; +import { Secret } from "aws-cdk-lib/aws-secretsmanager"; + + +export interface GarnetScorpioProps extends NestedStackProps{ + vpc: Vpc, + secret: Secret +} + +export class GarnetScorpio extends NestedStack { + public readonly dns_context_broker: string; + public readonly vpc: Vpc; + public readonly broker_api_endpoint: string; + public readonly api_ref: string; + + constructor(scope: Construct, id: string, props: GarnetScorpioProps) { + super(scope, id, props); + + // const database_construct = new GarnetScorpioDatabase(this, "Database", { + // vpc: props.vpc, + // secret_arn: props.secret.secretArn, + // }); + + // const fargate_construct = new GarnetScorpioFargate( + // this, + // "Fargate", + // { + // vpc: props.vpc, + // sg_database: database_construct.sg_database, + // secret_arn: props.secret.secretArn, + // db_endpoint: database_construct.database_endpoint, + // db_port: database_construct.database_port, + // image_context_broker: Parameters.garnet_scorpio.image_context_broker, + // } + // ); + + // fargate_construct.node.addDependency(database_construct) + + const api_stack = new GarnetApiGateway(this, "Api", { + vpc: props.vpc, + fargate_albListenerArn: Parameters.amazon_eks_cluster_load_balancer_listener_arn,//fargate_construct.fargate_alb, + }); + + new CfnOutput(this, "garnet_endpoint", { + value: `https://${api_stack.api_ref}.execute-api.${Aws.REGION}.amazonaws.com`, + }); + + this.broker_api_endpoint = `https://${api_stack.api_ref}.execute-api.${Aws.REGION}.amazonaws.com`; + this.dns_context_broker = + Parameters.amazon_eks_cluster_load_balancer_dns;//fargate_construct.fargate_alb.loadBalancer.loadBalancerDnsName; + this.vpc = props.vpc; + this.api_ref = api_stack.api_ref; + + // new CfnOutput(this, "fargate_alb", { + // value: fargate_construct.fargate_alb.loadBalancer.loadBalancerDnsName, + // }); + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package-lock.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package-lock.json new file mode 100644 index 0000000..bc67bd3 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package-lock.json @@ -0,0 +1,4288 @@ +{ + "name": "garnet", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "garnet", + "version": "1.0.0", + "hasInstallScript": true, + "dependencies": { + "aws-cdk-lib": "2.103.1", + "constructs": "^10.3.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "garnet": "bin/garnet.js" + }, + "devDependencies": { + "@types/jest": "^29.5.6", + "@types/node": "20.8.9", + "aws-cdk": "^2.103.1", + "constructs": "^10.3.0", + "jest": "^29.7.0", + "source-map-support": "^0.5.21", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "~5.2.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.201", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.201.tgz", + "integrity": "sha512-INZqcwDinNaIdb5CtW3ez5s943nX5stGBQS6VOP2JDlOFP81hM3fds/9NDknipqfUkZM43dx+HgVvkXYXXARCQ==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", + "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", + "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", + "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", + "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", + "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz", + "integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz", + "integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", + "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.8.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", + "integrity": "sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz", + "integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aws-cdk": { + "version": "2.103.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.103.1.tgz", + "integrity": "sha512-5DXPlMi8Gf/a6fMqwY6uVwuR21PDDNGkAGkMVTTuaiulc4RsMjQUGq8I4xaPIYQRvR+VHFU+JQ5j5PnI5/gBrA==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.103.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.103.1.tgz", + "integrity": "sha512-Y9P4dv+vdfTiqTGeYebajQ30OiPgsIDGonpaxjQYA1A4RYemYu62bX1J+QQBJh7FNd3amybD7YgJe3jKIUQuHw==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.200", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.1.1", + "ignore": "^5.2.4", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.0", + "semver": "^7.5.4", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.12.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.2.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.5.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.1", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001558", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz", + "integrity": "sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.569", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.569.tgz", + "integrity": "sha512-LsrJjZ0IbVy12ApW3gpYpcmHS3iRxH4bkKOW98y1/D+3cvDUWGcbzbsFinfUS8knpcZk/PG/2p/RnkMCYN7PVg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package.json new file mode 100644 index 0000000..c151ce6 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/package.json @@ -0,0 +1,30 @@ +{ + "name": "garnet", + "version": "1.0.0", + "bin": { + "garnet": "bin/garnet.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk", + "preinstall": "node install.js" + }, + "devDependencies": { + "@types/jest": "^29.5.6", + "@types/node": "20.8.9", + "aws-cdk": "^2.103.1", + "constructs": "^10.3.0", + "jest": "^29.7.0", + "source-map-support": "^0.5.21", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "~5.2.2" + }, + "dependencies": { + "aws-cdk-lib": "2.103.1", + "constructs": "^10.3.0", + "source-map-support": "^0.5.21" + } +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/parameters.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/parameters.ts new file mode 100644 index 0000000..4df6afb --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/parameters.ts @@ -0,0 +1,31 @@ +import { Aws } from "aws-cdk-lib" +import { InstanceClass, InstanceSize, InstanceType } from "aws-cdk-lib/aws-ec2" +import {StorageType } from "aws-cdk-lib/aws-rds" +import { Broker } from "./lib/stacks/garnet-constructs/constants" + +export const Parameters = { + // FIWARE DATA SPACE CONNECTOR PARAMETERS + amazon_eks_cluster_load_balancer_dns: "k8s-kubesyst-ingressn-XXXXXXXX.elb.eu-west-1.amazonaws.com", // REPLACE WITH YOUR CLUSTER LOAD BALANCER DNS + amazon_eks_cluster_load_balancer_listener_arn: "arn:aws:elasticloadbalancing:eu-west-1:XXXXXXXXX:listener/net/k8s-kubesyst-ingressn-XXXXXXXXXX/XXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXX", + + // GARNET PARAMETERS + aws_region: "eu-west-1", // see regions in which you can deploy Garnet: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vpc-links.html#http-api-vpc-link-availability + garnet_broker: Broker.SCORPIO, // DO NOT CHANGE + garnet_bucket: `garnet-datalake-${Aws.REGION}-${Aws.ACCOUNT_ID}`, // DO NOT CHANGE + smart_data_model_url : 'https://raw.githubusercontent.com/awslabs/garnet-framework/main/context.jsonld', + // FARGATE PARAMETERS + garnet_fargate: { + fargate_cpu: 1024, + fargate_memory_limit: 4096, + autoscale_requests_number: 200, + autoscale_min_capacity: 2, + autoscale_max_capacity: 10 + }, + // SCORPIO BROKER PARAMETERS + garnet_scorpio: { + image_context_broker: 'public.ecr.aws/garnet/scorpio:4.1.10', // Link to ECR Public gallery of Scorpio Broker image. + rds_instance_type: InstanceType.of( InstanceClass.BURSTABLE4_GRAVITON, InstanceSize.MEDIUM), // see https://aws.amazon.com/rds/instance-types/ + rds_storage_type: StorageType.GP3, // see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html + dbname: 'scorpio' + } +} \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/garnet-core.test.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/garnet-core.test.ts new file mode 100644 index 0000000..2c3c74b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/garnet-core.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Garnet from '../lib/garnet-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/garnet-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Garnet.GarnetStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/stf-core.test.ts b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/stf-core.test.ts new file mode 100644 index 0000000..2c3c74b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/test/stf-core.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Garnet from '../lib/garnet-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/garnet-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Garnet.GarnetStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/tsconfig.json b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/tsconfig.json new file mode 100644 index 0000000..aaa7dc5 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/aws-garnet-iot-module/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/podLogs/podLogsPlaceholder.txt b/doc/deployment-integration/aws-garnet/scenario-1-deployment/podLogs/podLogsPlaceholder.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario1.yaml b/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario1.yaml new file mode 100644 index 0000000..3e710a2 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario1.yaml @@ -0,0 +1,1777 @@ +# should argo-cd applications be created? +argoApplications: false + + +#Sub-Chart configuration + +activation-service: + # Enable the deployment of application: activation-service + deploymentEnabled: true + + activation-service: + ## Configuration of activation service execution + activationService: + # -- Number of (gunicorn) workers that should be created + workers: 1 + # -- Maximum header size in bytes + maxHeaderSize: 32768 + # -- Log Level + logLevel: "debug" + + ## Add Ingress or OpenShift Route + route: + enabled: false + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-as.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-as.dsba.aws.fiware.io + secretName: as-ips-dsba-tls + + ## CCS config + ccs: + endpoint: "http://ips-dsc-credentials-config-service:8080/" + id: "ips-activation-service" + defaultOidcScope: "default" + oidcScopes: + default: + - type: "VerifiableCredential" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + - type: "IpsActivationService" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + + ## AS config + config: + + # DB + db: + # -- Use sqlite in-memory database + useMemory: true + # -- Enable tracking of modifications + modTracking: false + # -- Enable SQL logging to stderr + echo: true + + # Configuration for additional API keys to protect certain endpoints + apikeys: + # Config for Trusted-Issuers-List flow + issuer: + # Header name + headerName: "AS-API-KEY" + # API key (auto-generated if left empty) + apiKey: "77ab4a67-ea3c-4348-98bd-2e9f0304bfb8" + # Enable for /issuer endpoint (API key will be required) + enabledIssuer: true + + issuer: + clientId: "ips-activation-service" + providerId: "did:web:ips.dsba.aws.fiware.io:did" + tilUri: "http://ips-dsc-trusted-issuers-list:8080" + verifierUri: "https://ips-verifier.dsba.aws.fiware.io" + samedevicePath: "/api/v1/samedevice" + jwksPath: "/.well-known/jwks" + algorithms: + - "ES256" + roles: + createRole: "CREATE_ISSUER" + updateRole: "UPDATE_ISSUER" + deleteRole: "DELETE_ISSUER" + +credentials-config-service: + # Enable the deployment of application: credentials-config-service + deploymentEnabled: true + + credentials-config-service: + + # Database config + database: + persistence: true + host: mysql-ips + name: ccs + + # Should use Secret in production environment + username: root + password: "dbPassword" + +dsba-pdp: + # Enable the deployment of application: dsba-pdp + deploymentEnabled: true + + dsba-pdp: + + # DB + db: + enabled: false + migrate: + enabled: false + + deployment: + # Log level + logLevel: DEBUG + + # iSHARE config + ishare: + existingSecret: ips-dsc-vcwaltid-tls-sec + + clientId: did:web:ips.dsba.aws.fiware.io:did + + # Initial list of fingerprints for trusted CAs. This will be overwritten + # after the first update from the trust anchor. + trustedFingerprints: + - D2F62092F982CF783D4632BD86FA86C3FBFDB2D8C8A58BC6809163FCF5CD030B + + ar: + id: "did:web:ips.dsba.aws.fiware.io:did" + delegationPath: "/ar/delegation" + tokenPath: "/oauth2/token" + url: "https://ar-ips.dsba.aws.fiware.io" + + trustAnchor: + id: "EU.EORI.FIWARESATELLITE" + tokenPath: "/token" + trustedListPath: "/trusted_list" + url: "https://tir.dsba.fiware.dev" + + # Verifier + trustedVerifiers: + - https://ips-verifier.dsba.aws.fiware.io/.well-known/jwks + + # Provider DID + providerId: "did:web:ips.dsba.aws.fiware.io:did" + + # ENVs + additionalEnvVars: + - name: ISHARE_CERTIFICATE_PATH + value: /iShare/tls.crt + - name: ISHARE_KEY_PATH + value: /iShare/tls.key + +kong: + # Enable the deployment of application: kong + deploymentEnabled: true + + kong: + replicaCount: 1 + + proxy: + enabled: true + tls: + enabled: false + + # Provide Ingress or Route config here + ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + #ingressClassName: nginx + tls: kong-ips-dsba-tls + hostname: ips-kong.dsba.aws.fiware.io + route: + enabled: false + + # Provide the kong.yml configuration (either as existing CM, secret or directly in the values.yaml) + dblessConfig: + configMap: "" + secret: "" + config: | + _format_version: "2.1" + _transform: true + + consumers: + - username: token-consumer + keyauth_credentials: + - tags: + - token-key + - tir-key + + services: + - host: "ips-dsc-orion" + name: "ips" + port: 1026 + protocol: http + + routes: + - name: ips + paths: + - /ips + strip_path: true + + plugins: + - name: pep-plugin + config: + pathprefix: "/ips" + authorizationendpointtype: ExtAuthz + authorizationendpointaddress: http://ips-dsc-dsba-pdp:8080/authz + + - name: request-transformer + config: + remove: + headers: + - Authorization + - authorization + +mongodb: + # Enable the deployment of application: mongodb + deploymentEnabled: true + + mongodb: + + # DB Authorization + auth: + enabled: true + # Should use a Secret on production deployments + rootPassword: "dbPassword" + + # Required for permissions to PVC + podSecurityContext: + enabled: true + fsGroup: 1001 + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsGroup: 0 + runAsNonRoot: true + + # Set resources + resources: + limits: + cpu: 200m + memory: 512Mi + + persistence: + enabled: true + size: 8Gi + +mysql: + # Enable the deployment of application: mysql + deploymentEnabled: true + + mysql: + fullnameOverride: mysql-ips + auth: + # Should use a Secret on production deployments + rootPassword: "dbPassword" + password: "dbPassword" + +orion-ld: + # Enable the deployment of application: orion-ld + deploymentEnabled: true + + orion: + + broker: + db: + auth: + user: root + password: "dbPassword" + mech: "SCRAM-SHA-1" + hosts: + - ips-dsc-mongodb + + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + entities: + - name: deliveryorder_happypets001.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS001", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "Customer Strasse 23" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-10-03" + }, + "pta": { + "type": "Property", + "value": "14:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-10-02" + }, + "eta": { + "type": "Property", + "value": "14:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + + - name: deliveryorder_happypets002.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS002", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets 2nd customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Hamburg", + "addressLocality": "Hamburg", + "postalCode": "23456", + "streetAddress": "Customer Str. 19" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-11-12" + }, + "pta": { + "type": "Property", + "value": "11:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-11-12" + }, + "eta": { + "type": "Property", + "value": "11:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + +postgres: + # Enable the deployment of application: postgres + deploymentEnabled: true + + postgresql: + + fullnameOverride: postgresql-ips + + auth: + # Should use a Secret for PWs on production deployments + # Credentials for Keycloak DB + username: keycloak + password: "dbPassword" + enablePostgresUser: true + + # Credentials for postgres admin user + postgresPassword: "dbRootPassword" + + # Init DB + primary: + initdb: + scripts: + create.sh: | + psql postgresql://postgres:${POSTGRES_POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE keycloak_ips;" + +trusted-issuers-list: + # Enable the deployment of application: trusted-issuers-list + deploymentEnabled: true + + trusted-issuers-list: + + # Ingress + ingress: + til: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: til-ips.dsba.aws.fiware.io + tls: + - hosts: + - til-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-til-tls + tir: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: tir-ips.dsba.aws.fiware.io + tls: + - hosts: + - tir-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-tir-tls + + # Database config + database: + persistence: true + host: mysql-ips + name: til + + # Should use Secret in production environment + username: root + password: "dbPassword" + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + issuers: + - name: mp_create + issuer: + did: "did:web:marketplace.dsba.fiware.dev:did" + credentials: + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "IpsActivationService" + claims: + - name: "roles" + allowedValues: + - - names: + - "CREATE_ISSUER" + target: "did:web:ips.dsba.aws.fiware.io:did" + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "VerifiableCredential" + +vcwaltid: + # Enable the deployment of application: vcwaltid + deploymentEnabled: true + + # Organisation DID + did: did:web:ips.dsba.aws.fiware.io:did + ingress: + enabled: true + host: ips.dsba.aws.fiware.io + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + tls: + enabled: true + route: + enabled: false + + # Walt-id config + vcwaltid: + + # Persistence + persistence: + enabled: true + pvc: + size: 1Gi + + # List of templates to be created + templates: + GaiaXParticipantCredential.json: | + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://registry.lab.dsba.eu/development/api/trusted-shape-registry/v1/shapes/jsonld/trustframework#" + ], + "type": [ + "VerifiableCredential" + ], + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuer": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuanceDate": "2023-03-21T12:00:00.148Z", + "credentialSubject": { + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "type": "gx:LegalParticipant", + "gx:legalName": "dsba compliant participant", + "gx:legalRegistrationNumber": { + "gx:vatID": "MYVATID" + }, + "gx:headquarterAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx:legalAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx-terms-and-conditions:gaiaxTermsAndConditions": "70c1d713215f95191a11d38fe2341faed27d19e083917bc8732ca4fea4976700" + } + } + NaturalPersonCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "familyName": "Happy", + "firstName": "User", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["VerifiableCredential", "LegalPersonCredential"] + } + MarketplaceUserCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["MarketplaceUserCredential"] + } + EmployeeCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["EmployeeCredential"] + } + +verifier: + # Enable the deployment of application: verifier + deploymentEnabled: true + + vcverifier: + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-verifier.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-verifier.dsba.aws.fiware.io + secretName: verifier-ips-dsba-tls + + deployment: + + # Logging + logging: + level: DEBUG + pathsToSkip: + - "/health" + + # Server config + server: + # Place external host here when publishing verifier with public URL + host: https://ips-verifier.dsba.aws.fiware.io + + # Walt-id config + ssikit: + auditorUrl: http://ips-dsc-vcwaltid:7003 + + # Verifier config + verifier: + # URL endpoint of data space trusted issuers registry + tirAddress: https://tir.dsba.fiware.dev/v3/issuers + # DID of organisation + did: did:web:ips.dsba.aws.fiware.io:did + + # Config service + configRepo: + configEndpoint: http://ips-dsc-credentials-config-service:8080/ + + +keyrock: + # Enable the deployment of application: keyrock + deploymentEnabled: true + + keyrock: + fullnameOverride: keyrock-ips + + # DB config + db: + user: root + password: "dbPassword" + host: mysql-ips + + # Admin user to be created + admin: + user: admin + password: "admin" + email: admin@fiware.org + + # External hostname of Keyrock + host: https://ar-ips.dsba.aws.fiware.io + + # Ingress + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ar-ips.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ar-ips.dsba.aws.fiware.io + secretName: ar-ips-dsba-tls + + ## Theme configuration for Keyrock + theme: + ## -- Enable theme + enabled: false + + ## Configuration of Authorisation Registry (AR) + authorisationRegistry: + # -- Enable usage of authorisation registry + enabled: true + # -- Identifier (EORI) of AR + identifier: "did:web:ips.dsba.aws.fiware.io:did" + # -- URL of AR + url: "internal" + + ## Configuration of iSHARE Satellite + satellite: + # -- Enable usage of satellite + enabled: true + # -- Identifier (EORI) of satellite + identifier: "EU.EORI.FIWARESATELLITE" + # -- URL of satellite + url: "https://tir.dsba.fiware.dev" + # -- Token endpoint of satellite + tokenEndpoint: "https://tir.dsba.fiware.dev/token" + # -- Parties endpoint of satellite + partiesEndpoint: "https://tir.dsba.fiware.dev/parties" + + ## -- Configuration of local key and certificate for validation and generation of tokens + token: + # -- Enable storage of local key and certificate + enabled: false + + # ENV variables for Keyrock + additionalEnvVars: + - name: IDM_TITLE + value: "IPS AR" + - name: IDM_DEBUG + value: "true" + - name: DEBUG + value: "*" + - name: IDM_DB_NAME + value: ar_idm_ips + - name: IDM_DB_SEED + value: "true" + - name: IDM_SERVER_MAX_HEADER_SIZE + value: "32768" + - name: IDM_PR_CLIENT_ID + value: "did:web:ips.dsba.aws.fiware.io:did" + - name: IDM_PR_CLIENT_KEY + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.key + - name: IDM_PR_CLIENT_CRT + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.crt + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + command: + - /bin/sh + - /scripts/create.sh + volumeMount: + name: scripts + mountPath: /scripts + env: + - name: DB_PASSWORD + value: "dbPassword" + scriptData: + create.sh: |- + mysql -h mysql-ips -u root -p$DB_PASSWORD ar_idm_ips <IPS Keycloak", + "enabled": true, + "attributes": { + "frontendUrl": "https://ips-kc.dsba.aws.fiware.io" + }, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges", + "composite": false, + "clientRole": false, + "containerId": "fiware-server", + "attributes": {} + } + ], + "client": { + "did:web:onboarding.dsba.fiware.dev:did": [ + { + "name": "LEGAL_REPRESENTATIVE", + "description": "Is allowed to register participants", + "clientRole": true + }, + { + "name": "EMPLOYEE", + "description": "Is allowed to see participants", + "clientRole": true + } + ], + "did:web:marketplace.dsba.fiware.dev:did": [ + { + "name": "customer", + "description": "Is allowed to buy.", + "clientRole": true + }, + { + "name": "seller", + "description": "Is allowed to offer.", + "clientRole": true + } + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + { + "name": "STANDARD_CUSTOMER", + "description": "User to access IPS with read access", + "clientRole": true + }, + { + "name": "GOLD_CUSTOMER", + "description": "User to access IPS with read/write access", + "clientRole": true + } + ] + } + }, + "groups": [ + { + "name": "admin", + "path": "/admin", + "realmRoles": [ + "user" + ] + }, + { + "name": "consumer", + "path": "/consumer", + "realmRoles": [ + "user" + ] + } + ], + "users": [ + { + "username": "the-lear", + "enabled": true, + "email": "lear@ips.org", + "credentials": [ + { + "type": "password", + "value": "the-lear" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE", + "EMPLOYEE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "legal-representative", + "enabled": true, + "email": "legal-representative@ips.org", + "firstName": "Legal", + "lastName": "IPSEmployee", + "credentials": [ + { + "type": "password", + "value": "legal-representative" + } + ], + "clientRoles": { + "did:web:marketplace.dsba.fiware.dev:did" : [ + "customer", + "seller" + ], + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "standard-employee", + "enabled": true, + "email": "standard-employee@ips.org", + "credentials": [ + { + "type": "password", + "value": "standard-employee" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "EMPLOYEE" + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + "GOLD_CUSTOMER" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/consumer" + ] + } + ], + "clients": [ + { + "clientId": "did:web:ips.dsba.aws.fiware.io:did", + "enabled": true, + "description": "Client for internal users", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_EmployeeCredential": "ldp_vc,jwt_vc_json", + "EmployeeCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:onboarding.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect the onboarding service at portal.dsba.fiware.dev", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_NaturalPersonCredential": "ldp_vc,jwt_vc_json", + "vctypes_GaiaXParticipantCredential": "ldp_vc,jwt_vc_json", + "vc_subjectDid": "did:web:packetdelivery.dsba.fiware.dev:did", + "vc_gx:legalName": "Packet Delivery Company Inc.", + "GaiaXParticipantCredential_claims": "subjectDid,gx:legalName", + "NaturalPersonCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } + ], + "clientScopes": [ + { + "name": "fiware-scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "fiware-scope-object", + "protocol": "openid-connect", + "protocolMapper": "oidc-script-based-protocol-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "fiware-scope-object", + "script": "/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * token - the current token\n * userSession - the current userSession\n * keycloakSession - the current userSession\n */\n\nvar ArrayList = Java.type(\"java.util.ArrayList\");\nvar fiware_scope = new ArrayList();\n\nvar forEach = Array.prototype.forEach;\n\nvar fiware_service;\nvar fiware_servicepath;\nvar fiware_entry;\nvar roles = '';\n\nvar orion_client = realm.getClientByClientId('orion-pep');\n\nfiware_service = user.getFirstAttribute('fiware-service');\nfiware_servicepath = user.getFirstAttribute('fiware-servicepath');\nif (fiware_service !== null && fiware_servicepath !== null) {\n\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = user.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n user.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n}\n\nforEach.call(\n user.getGroups().toArray(),\n function (group) {\n\n fiware_service = group.getFirstAttribute('fiware-service');\n fiware_servicepath = group.getFirstAttribute('fiware-servicepath');\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = group.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n } else if (group.getParentId() !== null) {\n fiware_service = group.getParent().getFirstAttribute('fiware-service');\n fiware_servicepath = group.getParent().getFirstAttribute('fiware-servicepath');\n\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n var subroleModels = group.getClientRoleMappings(orion_client);\n if (subroleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = '';\n }\n }\n }\n);\n\nexports = fiware_scope;" + } + } + ] + }, + { + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "roles", + "role_list", + "email", + "web-origins", + "profile" + ], + "defaultOptionalClientScopes": [ + "microprofile-jwt", + "phone", + "address", + "offline_access" + ] + } + + + + diff --git a/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml b/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml new file mode 100644 index 0000000..3d972bb --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-1-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario1.yaml @@ -0,0 +1,1777 @@ +# should argo-cd applications be created? +argoApplications: false + + +#Sub-Chart configuration + +activation-service: + # Enable the deployment of application: activation-service + deploymentEnabled: true + + activation-service: + ## Configuration of activation service execution + activationService: + # -- Number of (gunicorn) workers that should be created + workers: 1 + # -- Maximum header size in bytes + maxHeaderSize: 32768 + # -- Log Level + logLevel: "debug" + + ## Add Ingress or OpenShift Route + route: + enabled: false + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-as.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-as.dsba.aws.fiware.io + secretName: as-ips-dsba-tls + + ## CCS config + ccs: + endpoint: "http://ips-dsc-credentials-config-service:8080/" + id: "ips-activation-service" + defaultOidcScope: "default" + oidcScopes: + default: + - type: "VerifiableCredential" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + - type: "IpsActivationService" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + + ## AS config + config: + + # DB + db: + # -- Use sqlite in-memory database + useMemory: true + # -- Enable tracking of modifications + modTracking: false + # -- Enable SQL logging to stderr + echo: true + + # Configuration for additional API keys to protect certain endpoints + apikeys: + # Config for Trusted-Issuers-List flow + issuer: + # Header name + headerName: "AS-API-KEY" + # API key (auto-generated if left empty) + apiKey: "77ab4a67-ea3c-4348-98bd-2e9f0304bfb8" + # Enable for /issuer endpoint (API key will be required) + enabledIssuer: true + + issuer: + clientId: "ips-activation-service" + providerId: "did:web:ips.dsba.aws.fiware.io:did" + tilUri: "http://ips-dsc-trusted-issuers-list:8080" + verifierUri: "https://ips-verifier.dsba.aws.fiware.io" + samedevicePath: "/api/v1/samedevice" + jwksPath: "/.well-known/jwks" + algorithms: + - "ES256" + roles: + createRole: "CREATE_ISSUER" + updateRole: "UPDATE_ISSUER" + deleteRole: "DELETE_ISSUER" + +credentials-config-service: + # Enable the deployment of application: credentials-config-service + deploymentEnabled: true + + credentials-config-service: + + # Database config + database: + persistence: true + host: mysql-ips + name: ccs + + # Should use Secret in production environment + username: root + password: "dbPassword" + +dsba-pdp: + # Enable the deployment of application: dsba-pdp + deploymentEnabled: true + + dsba-pdp: + + # DB + db: + enabled: false + migrate: + enabled: false + + deployment: + # Log level + logLevel: DEBUG + + # iSHARE config + ishare: + existingSecret: ips-dsc-vcwaltid-tls-sec + + clientId: did:web:ips.dsba.aws.fiware.io:did + + # Initial list of fingerprints for trusted CAs. This will be overwritten + # after the first update from the trust anchor. + trustedFingerprints: + - D2F62092F982CF783D4632BD86FA86C3FBFDB2D8C8A58BC6809163FCF5CD030B + + ar: + id: "did:web:ips.dsba.aws.fiware.io:did" + delegationPath: "/ar/delegation" + tokenPath: "/oauth2/token" + url: "https://ar-ips.dsba.aws.fiware.io" + + trustAnchor: + id: "EU.EORI.FIWARESATELLITE" + tokenPath: "/token" + trustedListPath: "/trusted_list" + url: "https://tir.dsba.fiware.dev" + + # Verifier + trustedVerifiers: + - https://ips-verifier.dsba.aws.fiware.io/.well-known/jwks + + # Provider DID + providerId: "did:web:ips.dsba.aws.fiware.io:did" + + # ENVs + additionalEnvVars: + - name: ISHARE_CERTIFICATE_PATH + value: /iShare/tls.crt + - name: ISHARE_KEY_PATH + value: /iShare/tls.key + +kong: + # Enable the deployment of application: kong + deploymentEnabled: true + + kong: + replicaCount: 1 + + proxy: + enabled: true + tls: + enabled: false + + # Provide Ingress or Route config here + ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + #ingressClassName: nginx + tls: kong-ips-dsba-tls + hostname: ips-kong.dsba.aws.fiware.io + route: + enabled: false + + # Provide the kong.yml configuration (either as existing CM, secret or directly in the values.yaml) + dblessConfig: + configMap: "" + secret: "" + config: | + _format_version: "2.1" + _transform: true + + consumers: + - username: token-consumer + keyauth_credentials: + - tags: + - token-key + - tir-key + + services: + - host: "ips-dsc-orion" + name: "ips" + port: 1026 + protocol: http + + routes: + - name: ips + paths: + - /ips + strip_path: true + + plugins: + - name: pep-plugin + config: + pathprefix: "/ips" + authorizationendpointtype: ExtAuthz + authorizationendpointaddress: http://ips-dsc-dsba-pdp:8080/authz + + - name: request-transformer + config: + remove: + headers: + - Authorization + - authorization + +mongodb: + # Enable the deployment of application: mongodb + deploymentEnabled: true + + mongodb: + + # DB Authorization + auth: + enabled: true + # Should use a Secret on production deployments + rootPassword: "dbPassword" + + # Required for permissions to PVC + podSecurityContext: + enabled: true + fsGroup: 1001 + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsGroup: 0 + runAsNonRoot: true + + # Set resources + resources: + limits: + cpu: 200m + memory: 512Mi + + persistence: + enabled: true + size: 8Gi + +mysql: + # Enable the deployment of application: mysql + deploymentEnabled: true + + mysql: + fullnameOverride: mysql-ips + auth: + # Should use a Secret on production deployments + rootPassword: "dbPassword" + password: "dbPassword" + +orion-ld: + # Enable the deployment of application: orion-ld + deploymentEnabled: true + + orion: + + broker: + db: + auth: + user: root + password: "dbPassword" + mech: "SCRAM-SHA-1" + hosts: + - ips-dsc-mongodb + + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + entities: + - name: deliveryorder_happypets001.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS001", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "Customer Strasse 23" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-10-03" + }, + "pta": { + "type": "Property", + "value": "14:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-10-02" + }, + "eta": { + "type": "Property", + "value": "14:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + + - name: deliveryorder_happypets002.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS002", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets 2nd customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Hamburg", + "addressLocality": "Hamburg", + "postalCode": "23456", + "streetAddress": "Customer Str. 19" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-11-12" + }, + "pta": { + "type": "Property", + "value": "11:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-11-12" + }, + "eta": { + "type": "Property", + "value": "11:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + +postgres: + # Enable the deployment of application: postgres + deploymentEnabled: true + + postgresql: + + fullnameOverride: postgresql-ips + + auth: + # Should use a Secret for PWs on production deployments + # Credentials for Keycloak DB + username: keycloak + password: "dbPassword" + enablePostgresUser: true + + # Credentials for postgres admin user + postgresPassword: "dbRootPassword" + + # Init DB + primary: + initdb: + scripts: + create.sh: | + psql postgresql://postgres:${POSTGRES_POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE keycloak_ips;" + +trusted-issuers-list: + # Enable the deployment of application: trusted-issuers-list + deploymentEnabled: true + + trusted-issuers-list: + + # Ingress + ingress: + til: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: til-ips.dsba.aws.fiware.io + tls: + - hosts: + - til-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-til-tls + tir: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: tir-ips.dsba.aws.fiware.io + tls: + - hosts: + - tir-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-tir-tls + + # Database config + database: + persistence: true + host: mysql-ips + name: til + + # Should use Secret in production environment + username: root + password: "dbPassword" + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + issuers: + - name: mp_create + issuer: + did: "did:web:marketplace.dsba.fiware.dev:did" + credentials: + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "IpsActivationService" + claims: + - name: "roles" + allowedValues: + - - names: + - "CREATE_ISSUER" + target: "did:web:ips.dsba.aws.fiware.io:did" + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "VerifiableCredential" + +vcwaltid: + # Enable the deployment of application: vcwaltid + deploymentEnabled: true + + # Organisation DID + did: did:web:ips.dsba.aws.fiware.io:did + ingress: + enabled: true + host: ips.dsba.aws.fiware.io + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + tls: + enabled: true + route: + enabled: false + + # Walt-id config + vcwaltid: + + # Persistence + persistence: + enabled: true + pvc: + size: 1Gi + + # List of templates to be created + templates: + GaiaXParticipantCredential.json: | + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://registry.lab.dsba.eu/development/api/trusted-shape-registry/v1/shapes/jsonld/trustframework#" + ], + "type": [ + "VerifiableCredential" + ], + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuer": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuanceDate": "2023-03-21T12:00:00.148Z", + "credentialSubject": { + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "type": "gx:LegalParticipant", + "gx:legalName": "dsba compliant participant", + "gx:legalRegistrationNumber": { + "gx:vatID": "MYVATID" + }, + "gx:headquarterAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx:legalAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx-terms-and-conditions:gaiaxTermsAndConditions": "70c1d713215f95191a11d38fe2341faed27d19e083917bc8732ca4fea4976700" + } + } + NaturalPersonCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "familyName": "Happy", + "firstName": "User", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["VerifiableCredential", "LegalPersonCredential"] + } + MarketplaceUserCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["MarketplaceUserCredential"] + } + EmployeeCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["EmployeeCredential"] + } + +verifier: + # Enable the deployment of application: verifier + deploymentEnabled: true + + vcverifier: + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-verifier.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-verifier.dsba.aws.fiware.io + secretName: verifier-ips-dsba-tls + + deployment: + + # Logging + logging: + level: DEBUG + pathsToSkip: + - "/health" + + # Server config + server: + # Place external host here when publishing verifier with public URL + host: https://ips-verifier.dsba.aws.fiware.io + + # Walt-id config + ssikit: + auditorUrl: http://ips-dsc-vcwaltid:7003 + + # Verifier config + verifier: + # URL endpoint of data space trusted issuers registry + tirAddress: https://tir.dsba.fiware.dev/v3/issuers + # DID of organisation + did: did:web:ips.dsba.aws.fiware.io:did + + # Config service + configRepo: + configEndpoint: http://ips-dsc-credentials-config-service:8080/ + + +keyrock: + # Enable the deployment of application: keyrock + deploymentEnabled: true + + keyrock: + fullnameOverride: keyrock-ips + + # DB config + db: + user: root + password: "dbPassword" + host: mysql-ips + + # Admin user to be created + admin: + user: admin + password: "admin" + email: admin@fiware.org + + # External hostname of Keyrock + host: https://ar-ips.dsba.aws.fiware.io + + # Ingress + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ar-ips.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ar-ips.dsba.aws.fiware.io + secretName: ar-ips-dsba-tls + + ## Theme configuration for Keyrock + theme: + ## -- Enable theme + enabled: false + + ## Configuration of Authorisation Registry (AR) + authorisationRegistry: + # -- Enable usage of authorisation registry + enabled: true + # -- Identifier (EORI) of AR + identifier: "did:web:ips.dsba.aws.fiware.io:did" + # -- URL of AR + url: "internal" + + ## Configuration of iSHARE Satellite + satellite: + # -- Enable usage of satellite + enabled: true + # -- Identifier (EORI) of satellite + identifier: "EU.EORI.FIWARESATELLITE" + # -- URL of satellite + url: "https://tir.dsba.fiware.dev" + # -- Token endpoint of satellite + tokenEndpoint: "https://tir.dsba.fiware.dev/token" + # -- Parties endpoint of satellite + partiesEndpoint: "https://tir.dsba.fiware.dev/parties" + + ## -- Configuration of local key and certificate for validation and generation of tokens + token: + # -- Enable storage of local key and certificate + enabled: false + + # ENV variables for Keyrock + additionalEnvVars: + - name: IDM_TITLE + value: "IPS AR" + - name: IDM_DEBUG + value: "true" + - name: DEBUG + value: "*" + - name: IDM_DB_NAME + value: ar_idm_ips + - name: IDM_DB_SEED + value: "true" + - name: IDM_SERVER_MAX_HEADER_SIZE + value: "32768" + - name: IDM_PR_CLIENT_ID + value: "did:web:ips.dsba.aws.fiware.io:did" + - name: IDM_PR_CLIENT_KEY + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.key + - name: IDM_PR_CLIENT_CRT + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.crt + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + command: + - /bin/sh + - /scripts/create.sh + volumeMount: + name: scripts + mountPath: /scripts + env: + - name: DB_PASSWORD + value: "dbPassword" + scriptData: + create.sh: |- + mysql -h mysql-ips -u root -p$DB_PASSWORD ar_idm_ips <IPS Keycloak", + "enabled": true, + "attributes": { + "frontendUrl": "https://ips-kc.dsba.aws.fiware.io" + }, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges", + "composite": false, + "clientRole": false, + "containerId": "fiware-server", + "attributes": {} + } + ], + "client": { + "did:web:onboarding.dsba.fiware.dev:did": [ + { + "name": "LEGAL_REPRESENTATIVE", + "description": "Is allowed to register participants", + "clientRole": true + }, + { + "name": "EMPLOYEE", + "description": "Is allowed to see participants", + "clientRole": true + } + ], + "did:web:marketplace.dsba.fiware.dev:did": [ + { + "name": "customer", + "description": "Is allowed to buy.", + "clientRole": true + }, + { + "name": "seller", + "description": "Is allowed to offer.", + "clientRole": true + } + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + { + "name": "STANDARD_CUSTOMER", + "description": "User to access IPS with read access", + "clientRole": true + }, + { + "name": "GOLD_CUSTOMER", + "description": "User to access IPS with read/write access", + "clientRole": true + } + ] + } + }, + "groups": [ + { + "name": "admin", + "path": "/admin", + "realmRoles": [ + "user" + ] + }, + { + "name": "consumer", + "path": "/consumer", + "realmRoles": [ + "user" + ] + } + ], + "users": [ + { + "username": "the-lear", + "enabled": true, + "email": "lear@ips.org", + "credentials": [ + { + "type": "password", + "value": "the-lear" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE", + "EMPLOYEE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "legal-representative", + "enabled": true, + "email": "legal-representative@ips.org", + "firstName": "Legal", + "lastName": "IPSEmployee", + "credentials": [ + { + "type": "password", + "value": "legal-representative" + } + ], + "clientRoles": { + "did:web:marketplace.dsba.fiware.dev:did" : [ + "customer", + "seller" + ], + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "standard-employee", + "enabled": true, + "email": "standard-employee@ips.org", + "credentials": [ + { + "type": "password", + "value": "standard-employee" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "EMPLOYEE" + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + "GOLD_CUSTOMER" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/consumer" + ] + } + ], + "clients": [ + { + "clientId": "did:web:ips.dsba.aws.fiware.io:did", + "enabled": true, + "description": "Client for internal users", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_EmployeeCredential": "ldp_vc,jwt_vc_json", + "EmployeeCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:onboarding.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect the onboarding service at portal.dsba.fiware.dev", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_NaturalPersonCredential": "ldp_vc,jwt_vc_json", + "vctypes_GaiaXParticipantCredential": "ldp_vc,jwt_vc_json", + "vc_subjectDid": "did:web:packetdelivery.dsba.fiware.dev:did", + "vc_gx:legalName": "Packet Delivery Company Inc.", + "GaiaXParticipantCredential_claims": "subjectDid,gx:legalName", + "NaturalPersonCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } + ], + "clientScopes": [ + { + "name": "fiware-scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "fiware-scope-object", + "protocol": "openid-connect", + "protocolMapper": "oidc-script-based-protocol-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "fiware-scope-object", + "script": "/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * token - the current token\n * userSession - the current userSession\n * keycloakSession - the current userSession\n */\n\nvar ArrayList = Java.type(\"java.util.ArrayList\");\nvar fiware_scope = new ArrayList();\n\nvar forEach = Array.prototype.forEach;\n\nvar fiware_service;\nvar fiware_servicepath;\nvar fiware_entry;\nvar roles = '';\n\nvar orion_client = realm.getClientByClientId('orion-pep');\n\nfiware_service = user.getFirstAttribute('fiware-service');\nfiware_servicepath = user.getFirstAttribute('fiware-servicepath');\nif (fiware_service !== null && fiware_servicepath !== null) {\n\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = user.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n user.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n}\n\nforEach.call(\n user.getGroups().toArray(),\n function (group) {\n\n fiware_service = group.getFirstAttribute('fiware-service');\n fiware_servicepath = group.getFirstAttribute('fiware-servicepath');\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = group.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n } else if (group.getParentId() !== null) {\n fiware_service = group.getParent().getFirstAttribute('fiware-service');\n fiware_servicepath = group.getParent().getFirstAttribute('fiware-servicepath');\n\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n var subroleModels = group.getClientRoleMappings(orion_client);\n if (subroleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = '';\n }\n }\n }\n);\n\nexports = fiware_scope;" + } + } + ] + }, + { + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "roles", + "role_list", + "email", + "web-origins", + "profile" + ], + "defaultOptionalClientScopes": [ + "microprofile-jwt", + "phone", + "address", + "offline_access" + ] + } + + + + diff --git a/doc/deployment-integration/aws-garnet/scenario-2-deployment/README.md b/doc/deployment-integration/aws-garnet/scenario-2-deployment/README.md new file mode 100644 index 0000000..f426f97 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-2-deployment/README.md @@ -0,0 +1,112 @@ +# Integration with AWS Garnet Framework + +## 2/ Existing AWS Garnet Framework deployment in the AWS Account with a Context Broker on AWS ECS Fargate +For this scenario, it is recommended that a modified version of the Helm Chart for the Data Spaces Connector is deployed to a Kubernetes Cluster in the service Amazon Elastic Kubernetes Service ([AWS EKS](https://aws.amazon.com/eks/)). +In this case, considering that your environment for the AWS Garnet Framework was set up following the [official AWS GitHub Repository](https://github.com/awslabs/garnet-framework), the Context Broker is already hosted as an Amazon Elastic Container Service ([AWS ECS](https://aws.amazon.com/ecs/)) task in an AWS Fargate cluster and the integration to the Data Spaces Connector will be performed by deploying only this modified Helm Chart available [in this reference](./yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml). + +
+ +![Target Architecture for extending the deployment of an existing AWS Garnet Framework](../static-assets/garnet-ds-connector-scenario2.png) + +
+ +### IPS Service Provider Deployment in Amazon EKS +This section covers the setup of the prerequisites of the IPS Service Provider examples of this repository, available in [this reference](../service-provider-ips/README.md). + +#### Changes to the original Helm chart +[The edited version of the IPS Service Provider example Helm Chart](./yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml) contains 3 main differences for this scenario where an existing Context Broker is already deployed and must only by extended by the additional building blocks of the Data Spaces Connector: + +* Disable the deployment of the MongoDB database + +```shell +mongodb: + # Disable the deployment of application: mongodb + deploymentEnabled: false +``` + +* Disable the deployment of the Context Broker following the same steps + +```shell +orion-ld: + # Disable the deployment of application: orion-ld + deploymentEnabled: false +``` + +* Replace the host for the Kong proxy to point it to AWS Garnet Framework's Unified API based on API Gateway. The value for the host parameter can be found in your [AWS Cloud Formation](https://console.aws.amazon.com/cloudformation/home) Stack named `Garnet` > Outputs tab > `GarnetEndpoint` > Value. + +```shell + # Provide the kong.yml configuration (either as existing CM, secret or directly in the values.yaml) + dblessConfig: + configMap: "" + secret: "" + config: | + _format_version: "2.1" + _transform: true + + consumers: + - username: token-consumer + keyauth_credentials: + - tags: + - token-key + - tir-key + + #TODO - Replace here with the AWS Garnet Framework Unified API endpoint AND REMOVE THIS LINE + services: + - host: "xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com" + name: "ips" + port: 443 + protocol: http +``` + +#### Helm Chart install steps + +* IPS Kubernetes namespace creation + +```shell +kubectl create namespace ips +``` + +* Add FIWARE Data Space Connector Remote repository + +```shell +helm repo add dsc https://fiware-ops.github.io/data-space-connector/ +``` + +* Install the Helm Chart using the provided file `./yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml` [available in this repository](./yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml) + +```shell +helm install -n ips -f ./yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml ips-dsc dsc/data-space-connector +``` + +## Other Resources - Troubleshooting +Once the Data Space Connector is deployed via the Helm chart in your cluster, additional scripts are also provided in this [repository](../scripts/) to help any troubleshooting of your connector deployment. +Two main scripts are provided: + +* 1/ Save all pods logs from a EKS cluster namespace using `kubectl` to your local deployment machine under `./podLogs/` [in this repository structure](./podLogs/) +The script `kubectlLogsFromNamespace.sh` [link](../scripts/kubectlLogsFromNamespace.sh) runs a local process in your deployment machine to poll logs from all currently running pods under a namespace from your cluster and save locally for further analysis. +It can be manually modified to change the desired namespace to be analyzed + +```shell +#!/bin/bash + +NAMESPACE="ips" +``` + +and the corresponding polling period by changing the values in the script file before running it + +```shell + # Sleep for a few seconds before checking for new logs and pods again + sleep 3 +done +``` + +* 2/ Delete all currently running pods from an EKS cluster namespace using `kubectl` +The script `deleteAllPodsFromNamespace.sh` [link](../scripts/deleteAllPodsFromNamespace.sh) runs a local process in your deployment machine to force delete long-lived running pods from a failed deployment. +It can be manually modified to change the desired namespace to be analyzed + +```shell +#!/bin/bash + +# Set the fixed namespace +namespace="ips" +``` \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scenario-2-deployment/podLogs/podLogsPlaceholder.txt b/doc/deployment-integration/aws-garnet/scenario-2-deployment/podLogs/podLogsPlaceholder.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario2.yaml b/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario2.yaml new file mode 100644 index 0000000..2447468 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-awl-load-balancer-controller-scenario2.yaml @@ -0,0 +1,1777 @@ +# should argo-cd applications be created? +argoApplications: false + + +#Sub-Chart configuration + +activation-service: + # Enable the deployment of application: activation-service + deploymentEnabled: true + + activation-service: + ## Configuration of activation service execution + activationService: + # -- Number of (gunicorn) workers that should be created + workers: 1 + # -- Maximum header size in bytes + maxHeaderSize: 32768 + # -- Log Level + logLevel: "debug" + + ## Add Ingress or OpenShift Route + route: + enabled: false + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-as.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-as.dsba.aws.fiware.io + secretName: as-ips-dsba-tls + + ## CCS config + ccs: + endpoint: "http://ips-dsc-credentials-config-service:8080/" + id: "ips-activation-service" + defaultOidcScope: "default" + oidcScopes: + default: + - type: "VerifiableCredential" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + - type: "IpsActivationService" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + + ## AS config + config: + + # DB + db: + # -- Use sqlite in-memory database + useMemory: true + # -- Enable tracking of modifications + modTracking: false + # -- Enable SQL logging to stderr + echo: true + + # Configuration for additional API keys to protect certain endpoints + apikeys: + # Config for Trusted-Issuers-List flow + issuer: + # Header name + headerName: "AS-API-KEY" + # API key (auto-generated if left empty) + apiKey: "77ab4a67-ea3c-4348-98bd-2e9f0304bfb8" + # Enable for /issuer endpoint (API key will be required) + enabledIssuer: true + + issuer: + clientId: "ips-activation-service" + providerId: "did:web:ips.dsba.aws.fiware.io:did" + tilUri: "http://ips-dsc-trusted-issuers-list:8080" + verifierUri: "https://ips-verifier.dsba.aws.fiware.io" + samedevicePath: "/api/v1/samedevice" + jwksPath: "/.well-known/jwks" + algorithms: + - "ES256" + roles: + createRole: "CREATE_ISSUER" + updateRole: "UPDATE_ISSUER" + deleteRole: "DELETE_ISSUER" + +credentials-config-service: + # Enable the deployment of application: credentials-config-service + deploymentEnabled: true + + credentials-config-service: + + # Database config + database: + persistence: true + host: mysql-ips + name: ccs + + # Should use Secret in production environment + username: root + password: "dbPassword" + +dsba-pdp: + # Enable the deployment of application: dsba-pdp + deploymentEnabled: true + + dsba-pdp: + + # DB + db: + enabled: false + migrate: + enabled: false + + deployment: + # Log level + logLevel: DEBUG + + # iSHARE config + ishare: + existingSecret: ips-dsc-vcwaltid-tls-sec + + clientId: did:web:ips.dsba.aws.fiware.io:did + + # Initial list of fingerprints for trusted CAs. This will be overwritten + # after the first update from the trust anchor. + trustedFingerprints: + - D2F62092F982CF783D4632BD86FA86C3FBFDB2D8C8A58BC6809163FCF5CD030B + + ar: + id: "did:web:ips.dsba.aws.fiware.io:did" + delegationPath: "/ar/delegation" + tokenPath: "/oauth2/token" + url: "https://ar-ips.dsba.aws.fiware.io" + + trustAnchor: + id: "EU.EORI.FIWARESATELLITE" + tokenPath: "/token" + trustedListPath: "/trusted_list" + url: "https://tir.dsba.fiware.dev" + + # Verifier + trustedVerifiers: + - https://ips-verifier.dsba.aws.fiware.io/.well-known/jwks + + # Provider DID + providerId: "did:web:ips.dsba.aws.fiware.io:did" + + # ENVs + additionalEnvVars: + - name: ISHARE_CERTIFICATE_PATH + value: /iShare/tls.crt + - name: ISHARE_KEY_PATH + value: /iShare/tls.key + +kong: + # Enable the deployment of application: kong + deploymentEnabled: true + + kong: + replicaCount: 1 + + proxy: + enabled: true + tls: + enabled: false + + # Provide Ingress or Route config here + ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + #ingressClassName: nginx + tls: kong-ips-dsba-tls + hostname: ips-kong.dsba.aws.fiware.io + route: + enabled: false + + # Provide the kong.yml configuration (either as existing CM, secret or directly in the values.yaml) + dblessConfig: + configMap: "" + secret: "" + config: | + _format_version: "2.1" + _transform: true + + consumers: + - username: token-consumer + keyauth_credentials: + - tags: + - token-key + - tir-key + + services: + - host: "psr8tihlz1.execute-api.eu-west-1.amazonaws.com" + name: "ips" + port: 443 + protocol: http + + routes: + - name: ips + paths: + - /ips + strip_path: true + + plugins: + - name: pep-plugin + config: + pathprefix: "/ips" + authorizationendpointtype: ExtAuthz + authorizationendpointaddress: http://ips-dsc-dsba-pdp:8080/authz + + - name: request-transformer + config: + remove: + headers: + - Authorization + - authorization + +mongodb: + # Enable the deployment of application: mongodb + deploymentEnabled: false + + mongodb: + + # DB Authorization + auth: + enabled: true + # Should use a Secret on production deployments + rootPassword: "dbPassword" + + # Required for permissions to PVC + podSecurityContext: + enabled: true + fsGroup: 1001 + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsGroup: 0 + runAsNonRoot: true + + # Set resources + resources: + limits: + cpu: 200m + memory: 512Mi + + persistence: + enabled: true + size: 8Gi + +mysql: + # Enable the deployment of application: mysql + deploymentEnabled: true + + mysql: + fullnameOverride: mysql-ips + auth: + # Should use a Secret on production deployments + rootPassword: "dbPassword" + password: "dbPassword" + +orion-ld: + # Enable the deployment of application: orion-ld + deploymentEnabled: false + + orion: + + broker: + db: + auth: + user: root + password: "dbPassword" + mech: "SCRAM-SHA-1" + hosts: + - ips-dsc-mongodb + + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + entities: + - name: deliveryorder_happypets001.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS001", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "Customer Strasse 23" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-10-03" + }, + "pta": { + "type": "Property", + "value": "14:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-10-02" + }, + "eta": { + "type": "Property", + "value": "14:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + + - name: deliveryorder_happypets002.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS002", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets 2nd customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Hamburg", + "addressLocality": "Hamburg", + "postalCode": "23456", + "streetAddress": "Customer Str. 19" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-11-12" + }, + "pta": { + "type": "Property", + "value": "11:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-11-12" + }, + "eta": { + "type": "Property", + "value": "11:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + +postgres: + # Enable the deployment of application: postgres + deploymentEnabled: true + + postgresql: + + fullnameOverride: postgresql-ips + + auth: + # Should use a Secret for PWs on production deployments + # Credentials for Keycloak DB + username: keycloak + password: "dbPassword" + enablePostgresUser: true + + # Credentials for postgres admin user + postgresPassword: "dbRootPassword" + + # Init DB + primary: + initdb: + scripts: + create.sh: | + psql postgresql://postgres:${POSTGRES_POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE keycloak_ips;" + +trusted-issuers-list: + # Enable the deployment of application: trusted-issuers-list + deploymentEnabled: true + + trusted-issuers-list: + + # Ingress + ingress: + til: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: til-ips.dsba.aws.fiware.io + tls: + - hosts: + - til-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-til-tls + tir: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: tir-ips.dsba.aws.fiware.io + tls: + - hosts: + - tir-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-tir-tls + + # Database config + database: + persistence: true + host: mysql-ips + name: til + + # Should use Secret in production environment + username: root + password: "dbPassword" + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + issuers: + - name: mp_create + issuer: + did: "did:web:marketplace.dsba.fiware.dev:did" + credentials: + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "IpsActivationService" + claims: + - name: "roles" + allowedValues: + - - names: + - "CREATE_ISSUER" + target: "did:web:ips.dsba.aws.fiware.io:did" + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "VerifiableCredential" + +vcwaltid: + # Enable the deployment of application: vcwaltid + deploymentEnabled: true + + # Organisation DID + did: did:web:ips.dsba.aws.fiware.io:did + ingress: + enabled: true + host: ips.dsba.aws.fiware.io + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + tls: + enabled: true + route: + enabled: false + + # Walt-id config + vcwaltid: + + # Persistence + persistence: + enabled: true + pvc: + size: 1Gi + + # List of templates to be created + templates: + GaiaXParticipantCredential.json: | + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://registry.lab.dsba.eu/development/api/trusted-shape-registry/v1/shapes/jsonld/trustframework#" + ], + "type": [ + "VerifiableCredential" + ], + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuer": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuanceDate": "2023-03-21T12:00:00.148Z", + "credentialSubject": { + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "type": "gx:LegalParticipant", + "gx:legalName": "dsba compliant participant", + "gx:legalRegistrationNumber": { + "gx:vatID": "MYVATID" + }, + "gx:headquarterAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx:legalAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx-terms-and-conditions:gaiaxTermsAndConditions": "70c1d713215f95191a11d38fe2341faed27d19e083917bc8732ca4fea4976700" + } + } + NaturalPersonCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "familyName": "Happy", + "firstName": "User", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["VerifiableCredential", "LegalPersonCredential"] + } + MarketplaceUserCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["MarketplaceUserCredential"] + } + EmployeeCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["EmployeeCredential"] + } + +verifier: + # Enable the deployment of application: verifier + deploymentEnabled: true + + vcverifier: + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-verifier.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-verifier.dsba.aws.fiware.io + secretName: verifier-ips-dsba-tls + + deployment: + + # Logging + logging: + level: DEBUG + pathsToSkip: + - "/health" + + # Server config + server: + # Place external host here when publishing verifier with public URL + host: https://ips-verifier.dsba.aws.fiware.io + + # Walt-id config + ssikit: + auditorUrl: http://ips-dsc-vcwaltid:7003 + + # Verifier config + verifier: + # URL endpoint of data space trusted issuers registry + tirAddress: https://tir.dsba.fiware.dev/v3/issuers + # DID of organisation + did: did:web:ips.dsba.aws.fiware.io:did + + # Config service + configRepo: + configEndpoint: http://ips-dsc-credentials-config-service:8080/ + + +keyrock: + # Enable the deployment of application: keyrock + deploymentEnabled: true + + keyrock: + fullnameOverride: keyrock-ips + + # DB config + db: + user: root + password: "dbPassword" + host: mysql-ips + + # Admin user to be created + admin: + user: admin + password: "admin" + email: admin@fiware.org + + # External hostname of Keyrock + host: https://ar-ips.dsba.aws.fiware.io + + # Ingress + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ar-ips.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ar-ips.dsba.aws.fiware.io + secretName: ar-ips-dsba-tls + + ## Theme configuration for Keyrock + theme: + ## -- Enable theme + enabled: false + + ## Configuration of Authorisation Registry (AR) + authorisationRegistry: + # -- Enable usage of authorisation registry + enabled: true + # -- Identifier (EORI) of AR + identifier: "did:web:ips.dsba.aws.fiware.io:did" + # -- URL of AR + url: "internal" + + ## Configuration of iSHARE Satellite + satellite: + # -- Enable usage of satellite + enabled: true + # -- Identifier (EORI) of satellite + identifier: "EU.EORI.FIWARESATELLITE" + # -- URL of satellite + url: "https://tir.dsba.fiware.dev" + # -- Token endpoint of satellite + tokenEndpoint: "https://tir.dsba.fiware.dev/token" + # -- Parties endpoint of satellite + partiesEndpoint: "https://tir.dsba.fiware.dev/parties" + + ## -- Configuration of local key and certificate for validation and generation of tokens + token: + # -- Enable storage of local key and certificate + enabled: false + + # ENV variables for Keyrock + additionalEnvVars: + - name: IDM_TITLE + value: "IPS AR" + - name: IDM_DEBUG + value: "true" + - name: DEBUG + value: "*" + - name: IDM_DB_NAME + value: ar_idm_ips + - name: IDM_DB_SEED + value: "true" + - name: IDM_SERVER_MAX_HEADER_SIZE + value: "32768" + - name: IDM_PR_CLIENT_ID + value: "did:web:ips.dsba.aws.fiware.io:did" + - name: IDM_PR_CLIENT_KEY + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.key + - name: IDM_PR_CLIENT_CRT + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.crt + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + command: + - /bin/sh + - /scripts/create.sh + volumeMount: + name: scripts + mountPath: /scripts + env: + - name: DB_PASSWORD + value: "dbPassword" + scriptData: + create.sh: |- + mysql -h mysql-ips -u root -p$DB_PASSWORD ar_idm_ips <IPS Keycloak", + "enabled": true, + "attributes": { + "frontendUrl": "https://ips-kc.dsba.aws.fiware.io" + }, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges", + "composite": false, + "clientRole": false, + "containerId": "fiware-server", + "attributes": {} + } + ], + "client": { + "did:web:onboarding.dsba.fiware.dev:did": [ + { + "name": "LEGAL_REPRESENTATIVE", + "description": "Is allowed to register participants", + "clientRole": true + }, + { + "name": "EMPLOYEE", + "description": "Is allowed to see participants", + "clientRole": true + } + ], + "did:web:marketplace.dsba.fiware.dev:did": [ + { + "name": "customer", + "description": "Is allowed to buy.", + "clientRole": true + }, + { + "name": "seller", + "description": "Is allowed to offer.", + "clientRole": true + } + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + { + "name": "STANDARD_CUSTOMER", + "description": "User to access IPS with read access", + "clientRole": true + }, + { + "name": "GOLD_CUSTOMER", + "description": "User to access IPS with read/write access", + "clientRole": true + } + ] + } + }, + "groups": [ + { + "name": "admin", + "path": "/admin", + "realmRoles": [ + "user" + ] + }, + { + "name": "consumer", + "path": "/consumer", + "realmRoles": [ + "user" + ] + } + ], + "users": [ + { + "username": "the-lear", + "enabled": true, + "email": "lear@ips.org", + "credentials": [ + { + "type": "password", + "value": "the-lear" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE", + "EMPLOYEE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "legal-representative", + "enabled": true, + "email": "legal-representative@ips.org", + "firstName": "Legal", + "lastName": "IPSEmployee", + "credentials": [ + { + "type": "password", + "value": "legal-representative" + } + ], + "clientRoles": { + "did:web:marketplace.dsba.fiware.dev:did" : [ + "customer", + "seller" + ], + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "standard-employee", + "enabled": true, + "email": "standard-employee@ips.org", + "credentials": [ + { + "type": "password", + "value": "standard-employee" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "EMPLOYEE" + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + "GOLD_CUSTOMER" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/consumer" + ] + } + ], + "clients": [ + { + "clientId": "did:web:ips.dsba.aws.fiware.io:did", + "enabled": true, + "description": "Client for internal users", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_EmployeeCredential": "ldp_vc,jwt_vc_json", + "EmployeeCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:onboarding.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect the onboarding service at portal.dsba.fiware.dev", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_NaturalPersonCredential": "ldp_vc,jwt_vc_json", + "vctypes_GaiaXParticipantCredential": "ldp_vc,jwt_vc_json", + "vc_subjectDid": "did:web:packetdelivery.dsba.fiware.dev:did", + "vc_gx:legalName": "Packet Delivery Company Inc.", + "GaiaXParticipantCredential_claims": "subjectDid,gx:legalName", + "NaturalPersonCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } + ], + "clientScopes": [ + { + "name": "fiware-scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "fiware-scope-object", + "protocol": "openid-connect", + "protocolMapper": "oidc-script-based-protocol-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "fiware-scope-object", + "script": "/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * token - the current token\n * userSession - the current userSession\n * keycloakSession - the current userSession\n */\n\nvar ArrayList = Java.type(\"java.util.ArrayList\");\nvar fiware_scope = new ArrayList();\n\nvar forEach = Array.prototype.forEach;\n\nvar fiware_service;\nvar fiware_servicepath;\nvar fiware_entry;\nvar roles = '';\n\nvar orion_client = realm.getClientByClientId('orion-pep');\n\nfiware_service = user.getFirstAttribute('fiware-service');\nfiware_servicepath = user.getFirstAttribute('fiware-servicepath');\nif (fiware_service !== null && fiware_servicepath !== null) {\n\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = user.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n user.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n}\n\nforEach.call(\n user.getGroups().toArray(),\n function (group) {\n\n fiware_service = group.getFirstAttribute('fiware-service');\n fiware_servicepath = group.getFirstAttribute('fiware-servicepath');\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = group.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n } else if (group.getParentId() !== null) {\n fiware_service = group.getParent().getFirstAttribute('fiware-service');\n fiware_servicepath = group.getParent().getFirstAttribute('fiware-servicepath');\n\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n var subroleModels = group.getClientRoleMappings(orion_client);\n if (subroleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = '';\n }\n }\n }\n);\n\nexports = fiware_scope;" + } + } + ] + }, + { + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "roles", + "role_list", + "email", + "web-origins", + "profile" + ], + "defaultOptionalClientScopes": [ + "microprofile-jwt", + "phone", + "address", + "offline_access" + ] + } + + + + diff --git a/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml b/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml new file mode 100644 index 0000000..2447468 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scenario-2-deployment/yaml/values-dsc-aws-load-balancer-controller-scenario2.yaml @@ -0,0 +1,1777 @@ +# should argo-cd applications be created? +argoApplications: false + + +#Sub-Chart configuration + +activation-service: + # Enable the deployment of application: activation-service + deploymentEnabled: true + + activation-service: + ## Configuration of activation service execution + activationService: + # -- Number of (gunicorn) workers that should be created + workers: 1 + # -- Maximum header size in bytes + maxHeaderSize: 32768 + # -- Log Level + logLevel: "debug" + + ## Add Ingress or OpenShift Route + route: + enabled: false + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-as.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-as.dsba.aws.fiware.io + secretName: as-ips-dsba-tls + + ## CCS config + ccs: + endpoint: "http://ips-dsc-credentials-config-service:8080/" + id: "ips-activation-service" + defaultOidcScope: "default" + oidcScopes: + default: + - type: "VerifiableCredential" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + - type: "IpsActivationService" + trustedParticipantsLists: [ + "https://tir.dsba.fiware.dev" + ] + trustedIssuersLists: [ + "http://ips-dsc-trusted-issuers-list:8080" + ] + + ## AS config + config: + + # DB + db: + # -- Use sqlite in-memory database + useMemory: true + # -- Enable tracking of modifications + modTracking: false + # -- Enable SQL logging to stderr + echo: true + + # Configuration for additional API keys to protect certain endpoints + apikeys: + # Config for Trusted-Issuers-List flow + issuer: + # Header name + headerName: "AS-API-KEY" + # API key (auto-generated if left empty) + apiKey: "77ab4a67-ea3c-4348-98bd-2e9f0304bfb8" + # Enable for /issuer endpoint (API key will be required) + enabledIssuer: true + + issuer: + clientId: "ips-activation-service" + providerId: "did:web:ips.dsba.aws.fiware.io:did" + tilUri: "http://ips-dsc-trusted-issuers-list:8080" + verifierUri: "https://ips-verifier.dsba.aws.fiware.io" + samedevicePath: "/api/v1/samedevice" + jwksPath: "/.well-known/jwks" + algorithms: + - "ES256" + roles: + createRole: "CREATE_ISSUER" + updateRole: "UPDATE_ISSUER" + deleteRole: "DELETE_ISSUER" + +credentials-config-service: + # Enable the deployment of application: credentials-config-service + deploymentEnabled: true + + credentials-config-service: + + # Database config + database: + persistence: true + host: mysql-ips + name: ccs + + # Should use Secret in production environment + username: root + password: "dbPassword" + +dsba-pdp: + # Enable the deployment of application: dsba-pdp + deploymentEnabled: true + + dsba-pdp: + + # DB + db: + enabled: false + migrate: + enabled: false + + deployment: + # Log level + logLevel: DEBUG + + # iSHARE config + ishare: + existingSecret: ips-dsc-vcwaltid-tls-sec + + clientId: did:web:ips.dsba.aws.fiware.io:did + + # Initial list of fingerprints for trusted CAs. This will be overwritten + # after the first update from the trust anchor. + trustedFingerprints: + - D2F62092F982CF783D4632BD86FA86C3FBFDB2D8C8A58BC6809163FCF5CD030B + + ar: + id: "did:web:ips.dsba.aws.fiware.io:did" + delegationPath: "/ar/delegation" + tokenPath: "/oauth2/token" + url: "https://ar-ips.dsba.aws.fiware.io" + + trustAnchor: + id: "EU.EORI.FIWARESATELLITE" + tokenPath: "/token" + trustedListPath: "/trusted_list" + url: "https://tir.dsba.fiware.dev" + + # Verifier + trustedVerifiers: + - https://ips-verifier.dsba.aws.fiware.io/.well-known/jwks + + # Provider DID + providerId: "did:web:ips.dsba.aws.fiware.io:did" + + # ENVs + additionalEnvVars: + - name: ISHARE_CERTIFICATE_PATH + value: /iShare/tls.crt + - name: ISHARE_KEY_PATH + value: /iShare/tls.key + +kong: + # Enable the deployment of application: kong + deploymentEnabled: true + + kong: + replicaCount: 1 + + proxy: + enabled: true + tls: + enabled: false + + # Provide Ingress or Route config here + ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + #ingressClassName: nginx + tls: kong-ips-dsba-tls + hostname: ips-kong.dsba.aws.fiware.io + route: + enabled: false + + # Provide the kong.yml configuration (either as existing CM, secret or directly in the values.yaml) + dblessConfig: + configMap: "" + secret: "" + config: | + _format_version: "2.1" + _transform: true + + consumers: + - username: token-consumer + keyauth_credentials: + - tags: + - token-key + - tir-key + + services: + - host: "psr8tihlz1.execute-api.eu-west-1.amazonaws.com" + name: "ips" + port: 443 + protocol: http + + routes: + - name: ips + paths: + - /ips + strip_path: true + + plugins: + - name: pep-plugin + config: + pathprefix: "/ips" + authorizationendpointtype: ExtAuthz + authorizationendpointaddress: http://ips-dsc-dsba-pdp:8080/authz + + - name: request-transformer + config: + remove: + headers: + - Authorization + - authorization + +mongodb: + # Enable the deployment of application: mongodb + deploymentEnabled: false + + mongodb: + + # DB Authorization + auth: + enabled: true + # Should use a Secret on production deployments + rootPassword: "dbPassword" + + # Required for permissions to PVC + podSecurityContext: + enabled: true + fsGroup: 1001 + containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsGroup: 0 + runAsNonRoot: true + + # Set resources + resources: + limits: + cpu: 200m + memory: 512Mi + + persistence: + enabled: true + size: 8Gi + +mysql: + # Enable the deployment of application: mysql + deploymentEnabled: true + + mysql: + fullnameOverride: mysql-ips + auth: + # Should use a Secret on production deployments + rootPassword: "dbPassword" + password: "dbPassword" + +orion-ld: + # Enable the deployment of application: orion-ld + deploymentEnabled: false + + orion: + + broker: + db: + auth: + user: root + password: "dbPassword" + mech: "SCRAM-SHA-1" + hosts: + - ips-dsc-mongodb + + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + entities: + - name: deliveryorder_happypets001.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS001", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "Customer Strasse 23" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-10-03" + }, + "pta": { + "type": "Property", + "value": "14:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-10-02" + }, + "eta": { + "type": "Property", + "value": "14:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + + - name: deliveryorder_happypets002.json + data: | + { + "id": "urn:ngsi-ld:DELIVERYORDER:HAPPYPETS002", + "type": "DELIVERYORDER", + "issuer": { + "type": "Property", + "value": "Happy Pets" + }, + "destinee": { + "type": "Property", + "value": "Happy Pets 2nd customer via IPS" + }, + "deliveryAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Hamburg", + "addressLocality": "Hamburg", + "postalCode": "23456", + "streetAddress": "Customer Str. 19" + } + }, + "originAddress": { + "type": "Property", + "value": { + "addressCountry": "DE", + "addressRegion": "Berlin", + "addressLocality": "Berlin", + "postalCode": "12345", + "streetAddress": "HappyPets Strasse 15" + } + }, + "pda": { + "type": "Property", + "value": "2021-11-12" + }, + "pta": { + "type": "Property", + "value": "11:00:00" + }, + "eda": { + "type": "Property", + "value": "2021-11-12" + }, + "eta": { + "type": "Property", + "value": "11:00:00" + }, + "@context": [ + "https://schema.lab.fiware.org/ld/context" + ] + } + +postgres: + # Enable the deployment of application: postgres + deploymentEnabled: true + + postgresql: + + fullnameOverride: postgresql-ips + + auth: + # Should use a Secret for PWs on production deployments + # Credentials for Keycloak DB + username: keycloak + password: "dbPassword" + enablePostgresUser: true + + # Credentials for postgres admin user + postgresPassword: "dbRootPassword" + + # Init DB + primary: + initdb: + scripts: + create.sh: | + psql postgresql://postgres:${POSTGRES_POSTGRES_PASSWORD}@localhost:5432 -c "CREATE DATABASE keycloak_ips;" + +trusted-issuers-list: + # Enable the deployment of application: trusted-issuers-list + deploymentEnabled: true + + trusted-issuers-list: + + # Ingress + ingress: + til: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: til-ips.dsba.aws.fiware.io + tls: + - hosts: + - til-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-til-tls + tir: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: tir-ips.dsba.aws.fiware.io + tls: + - hosts: + - tir-ips.dsba.aws.fiware.io + secretName: til-ips-dsba-tir-tls + + # Database config + database: + persistence: true + host: mysql-ips + name: til + + # Should use Secret in production environment + username: root + password: "dbPassword" + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + issuers: + - name: mp_create + issuer: + did: "did:web:marketplace.dsba.fiware.dev:did" + credentials: + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "IpsActivationService" + claims: + - name: "roles" + allowedValues: + - - names: + - "CREATE_ISSUER" + target: "did:web:ips.dsba.aws.fiware.io:did" + - validFor: + from: "2022-07-21T17:32:28Z" + to: "2040-07-21T17:32:28Z" + credentialsType: "VerifiableCredential" + +vcwaltid: + # Enable the deployment of application: vcwaltid + deploymentEnabled: true + + # Organisation DID + did: did:web:ips.dsba.aws.fiware.io:did + ingress: + enabled: true + host: ips.dsba.aws.fiware.io + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + tls: + enabled: true + route: + enabled: false + + # Walt-id config + vcwaltid: + + # Persistence + persistence: + enabled: true + pvc: + size: 1Gi + + # List of templates to be created + templates: + GaiaXParticipantCredential.json: | + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://registry.lab.dsba.eu/development/api/trusted-shape-registry/v1/shapes/jsonld/trustframework#" + ], + "type": [ + "VerifiableCredential" + ], + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuer": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "issuanceDate": "2023-03-21T12:00:00.148Z", + "credentialSubject": { + "id": "did:web:raw.githubusercontent.com:egavard:payload-sign:master", + "type": "gx:LegalParticipant", + "gx:legalName": "dsba compliant participant", + "gx:legalRegistrationNumber": { + "gx:vatID": "MYVATID" + }, + "gx:headquarterAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx:legalAddress": { + "gx:countrySubdivisionCode": "BE-BRU" + }, + "gx-terms-and-conditions:gaiaxTermsAndConditions": "70c1d713215f95191a11d38fe2341faed27d19e083917bc8732ca4fea4976700" + } + } + NaturalPersonCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "familyName": "Happy", + "firstName": "User", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["VerifiableCredential", "LegalPersonCredential"] + } + MarketplaceUserCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["MarketplaceUserCredential"] + } + EmployeeCredential.json: | + { + "@context": ["https://www.w3.org/2018/credentials/v1"], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/FIWARE-Ops/tech-x-challenge/main/schema.json", + "type": "FullJsonSchemaValidator2021" + }, + "credentialSubject": { + "type": "gx:NaturalParticipant", + "email": "normal-user@fiware.org", + "familyName": "IPS", + "firstName": "employee", + "lastName": "IPS", + "roles": [{ + "names": ["LEGAL_REPRESENTATIVE"], + "target": "did:web:onboarding" + }] + }, + "id": "urn:uuid:3add94f4-28ec-42a1-8704-4e4aa51006b4", + "issued": "2021-08-31T00:00:00Z", + "issuer": "did:ebsi:2A9BZ9SUe6BatacSpvs1V5CdjHvLpQ7bEsi2Jb6LdHKnQxaN", + "validFrom": "2021-08-31T00:00:00Z", + "issuanceDate": "2021-08-31T00:00:00Z", + "type": ["EmployeeCredential"] + } + +verifier: + # Enable the deployment of application: verifier + deploymentEnabled: true + + vcverifier: + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ips-verifier.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ips-verifier.dsba.aws.fiware.io + secretName: verifier-ips-dsba-tls + + deployment: + + # Logging + logging: + level: DEBUG + pathsToSkip: + - "/health" + + # Server config + server: + # Place external host here when publishing verifier with public URL + host: https://ips-verifier.dsba.aws.fiware.io + + # Walt-id config + ssikit: + auditorUrl: http://ips-dsc-vcwaltid:7003 + + # Verifier config + verifier: + # URL endpoint of data space trusted issuers registry + tirAddress: https://tir.dsba.fiware.dev/v3/issuers + # DID of organisation + did: did:web:ips.dsba.aws.fiware.io:did + + # Config service + configRepo: + configEndpoint: http://ips-dsc-credentials-config-service:8080/ + + +keyrock: + # Enable the deployment of application: keyrock + deploymentEnabled: true + + keyrock: + fullnameOverride: keyrock-ips + + # DB config + db: + user: root + password: "dbPassword" + host: mysql-ips + + # Admin user to be created + admin: + user: admin + password: "admin" + email: admin@fiware.org + + # External hostname of Keyrock + host: https://ar-ips.dsba.aws.fiware.io + + # Ingress + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: letsencrypt-fiware-eks + kubernetes.io/ingress.class: nginx + hosts: + - host: ar-ips.dsba.aws.fiware.io + paths: + - / + tls: + - hosts: + - ar-ips.dsba.aws.fiware.io + secretName: ar-ips-dsba-tls + + ## Theme configuration for Keyrock + theme: + ## -- Enable theme + enabled: false + + ## Configuration of Authorisation Registry (AR) + authorisationRegistry: + # -- Enable usage of authorisation registry + enabled: true + # -- Identifier (EORI) of AR + identifier: "did:web:ips.dsba.aws.fiware.io:did" + # -- URL of AR + url: "internal" + + ## Configuration of iSHARE Satellite + satellite: + # -- Enable usage of satellite + enabled: true + # -- Identifier (EORI) of satellite + identifier: "EU.EORI.FIWARESATELLITE" + # -- URL of satellite + url: "https://tir.dsba.fiware.dev" + # -- Token endpoint of satellite + tokenEndpoint: "https://tir.dsba.fiware.dev/token" + # -- Parties endpoint of satellite + partiesEndpoint: "https://tir.dsba.fiware.dev/parties" + + ## -- Configuration of local key and certificate for validation and generation of tokens + token: + # -- Enable storage of local key and certificate + enabled: false + + # ENV variables for Keyrock + additionalEnvVars: + - name: IDM_TITLE + value: "IPS AR" + - name: IDM_DEBUG + value: "true" + - name: DEBUG + value: "*" + - name: IDM_DB_NAME + value: ar_idm_ips + - name: IDM_DB_SEED + value: "true" + - name: IDM_SERVER_MAX_HEADER_SIZE + value: "32768" + - name: IDM_PR_CLIENT_ID + value: "did:web:ips.dsba.aws.fiware.io:did" + - name: IDM_PR_CLIENT_KEY + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.key + - name: IDM_PR_CLIENT_CRT + valueFrom: + secretKeyRef: + name: ips-dsc-vcwaltid-tls-sec + key: tls.crt + + # Init data + initData: + initEnabled: true + hook: post-install + backoffLimit: 6 + command: + - /bin/sh + - /scripts/create.sh + volumeMount: + name: scripts + mountPath: /scripts + env: + - name: DB_PASSWORD + value: "dbPassword" + scriptData: + create.sh: |- + mysql -h mysql-ips -u root -p$DB_PASSWORD ar_idm_ips <IPS Keycloak", + "enabled": true, + "attributes": { + "frontendUrl": "https://ips-kc.dsba.aws.fiware.io" + }, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "user", + "description": "User privileges", + "composite": false, + "clientRole": false, + "containerId": "fiware-server", + "attributes": {} + } + ], + "client": { + "did:web:onboarding.dsba.fiware.dev:did": [ + { + "name": "LEGAL_REPRESENTATIVE", + "description": "Is allowed to register participants", + "clientRole": true + }, + { + "name": "EMPLOYEE", + "description": "Is allowed to see participants", + "clientRole": true + } + ], + "did:web:marketplace.dsba.fiware.dev:did": [ + { + "name": "customer", + "description": "Is allowed to buy.", + "clientRole": true + }, + { + "name": "seller", + "description": "Is allowed to offer.", + "clientRole": true + } + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + { + "name": "STANDARD_CUSTOMER", + "description": "User to access IPS with read access", + "clientRole": true + }, + { + "name": "GOLD_CUSTOMER", + "description": "User to access IPS with read/write access", + "clientRole": true + } + ] + } + }, + "groups": [ + { + "name": "admin", + "path": "/admin", + "realmRoles": [ + "user" + ] + }, + { + "name": "consumer", + "path": "/consumer", + "realmRoles": [ + "user" + ] + } + ], + "users": [ + { + "username": "the-lear", + "enabled": true, + "email": "lear@ips.org", + "credentials": [ + { + "type": "password", + "value": "the-lear" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE", + "EMPLOYEE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "legal-representative", + "enabled": true, + "email": "legal-representative@ips.org", + "firstName": "Legal", + "lastName": "IPSEmployee", + "credentials": [ + { + "type": "password", + "value": "legal-representative" + } + ], + "clientRoles": { + "did:web:marketplace.dsba.fiware.dev:did" : [ + "customer", + "seller" + ], + "did:web:onboarding.dsba.fiware.dev:did": [ + "LEGAL_REPRESENTATIVE" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/admin", + "/consumer" + ] + }, + { + "username": "standard-employee", + "enabled": true, + "email": "standard-employee@ips.org", + "credentials": [ + { + "type": "password", + "value": "standard-employee" + } + ], + "clientRoles": { + "did:web:onboarding.dsba.fiware.dev:did": [ + "EMPLOYEE" + ], + "did:web:ips.dsba.aws.fiware.io:did": [ + "GOLD_CUSTOMER" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + "/consumer" + ] + } + ], + "clients": [ + { + "clientId": "did:web:ips.dsba.aws.fiware.io:did", + "enabled": true, + "description": "Client for internal users", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_EmployeeCredential": "ldp_vc,jwt_vc_json", + "EmployeeCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:onboarding.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect the onboarding service at portal.dsba.fiware.dev", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_NaturalPersonCredential": "ldp_vc,jwt_vc_json", + "vctypes_GaiaXParticipantCredential": "ldp_vc,jwt_vc_json", + "vc_subjectDid": "did:web:packetdelivery.dsba.fiware.dev:did", + "vc_gx:legalName": "Packet Delivery Company Inc.", + "GaiaXParticipantCredential_claims": "subjectDid,gx:legalName", + "NaturalPersonCredential_claims": "email,firstName,familyName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "did:web:marketplace.dsba.fiware.dev:did", + "enabled": true, + "description": "Client to connect to the marketplace", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "SIOP-2", + "attributes": { + "client.secret.creation.time": "1675260539", + "expiryInMin": "3600", + "vctypes_MarketplaceUserCredential": "ldp_vc,jwt_vc_json", + "MarketplaceUserCredential_claims": "email,firstName,lastName,roles" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } + ], + "clientScopes": [ + { + "name": "fiware-scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "fiware-scope-object", + "protocol": "openid-connect", + "protocolMapper": "oidc-script-based-protocol-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "fiware-scope-object", + "script": "/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * token - the current token\n * userSession - the current userSession\n * keycloakSession - the current userSession\n */\n\nvar ArrayList = Java.type(\"java.util.ArrayList\");\nvar fiware_scope = new ArrayList();\n\nvar forEach = Array.prototype.forEach;\n\nvar fiware_service;\nvar fiware_servicepath;\nvar fiware_entry;\nvar roles = '';\n\nvar orion_client = realm.getClientByClientId('orion-pep');\n\nfiware_service = user.getFirstAttribute('fiware-service');\nfiware_servicepath = user.getFirstAttribute('fiware-servicepath');\nif (fiware_service !== null && fiware_servicepath !== null) {\n\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = user.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n user.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n}\n\nforEach.call(\n user.getGroups().toArray(),\n function (group) {\n\n fiware_service = group.getFirstAttribute('fiware-service');\n fiware_servicepath = group.getFirstAttribute('fiware-servicepath');\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n\n var roleModels = group.getClientRoleMappings(orion_client);\n if (roleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = {};\n } else if (group.getParentId() !== null) {\n fiware_service = group.getParent().getFirstAttribute('fiware-service');\n fiware_servicepath = group.getParent().getFirstAttribute('fiware-servicepath');\n\n if (fiware_service !== null && fiware_servicepath !== null) {\n fiware_entry = {\n \"fiware-service\": fiware_service,\n \"fiware-servicepath\": fiware_servicepath\n };\n var subroleModels = group.getClientRoleMappings(orion_client);\n if (subroleModels.size() > 0) {\n forEach.call(\n group.getClientRoleMappings(orion_client).toArray(),\n function (role) {\n roles = roles + role.getName() + \",\";\n }\n );\n roles = roles.substring(0, roles.length - 1);\n fiware_entry[\"orion-roles\"] = roles;\n roles = '';\n }\n\n fiware_scope.add(JSON.stringify(fiware_entry));\n fiware_entry = '';\n }\n }\n }\n);\n\nexports = fiware_scope;" + } + } + ] + }, + { + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "roles", + "role_list", + "email", + "web-origins", + "profile" + ], + "defaultOptionalClientScopes": [ + "microprofile-jwt", + "phone", + "address", + "offline_access" + ] + } + + + + diff --git a/doc/deployment-integration/aws-garnet/scripts/deleteAllPodsFromNamespace.sh b/doc/deployment-integration/aws-garnet/scripts/deleteAllPodsFromNamespace.sh new file mode 100755 index 0000000..3cdf8ce --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scripts/deleteAllPodsFromNamespace.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Set the fixed namespace +namespace="ips" + +# Check if kubectl is installed +if ! command -v kubectl &> /dev/null; then + echo "kubectl not found. Please install kubectl before running this script." + exit 1 +fi + +# Confirm with the user before proceeding +read -p "This will delete all pods in the '$namespace' namespace. Are you sure? (y/n): " confirm +if [[ $confirm != "y" ]]; then + echo "Operation canceled." + exit 0 +fi + +# Delete all pods in the specified namespace +kubectl delete pods --namespace="$namespace" --all + +echo "Deleting pods in the '$namespace' namespace..." diff --git a/doc/deployment-integration/aws-garnet/scripts/eks-cluster-fargateProfiler.sh b/doc/deployment-integration/aws-garnet/scripts/eks-cluster-fargateProfiler.sh new file mode 100755 index 0000000..55f246b --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scripts/eks-cluster-fargateProfiler.sh @@ -0,0 +1,44 @@ +cat << EOF > ./yaml/eks-cluster-3az.yaml +# A simple example of ClusterConfig object: +--- +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig + +metadata: + name: ${ekscluster_name} + region: ${AWS_REGION} + version: "${eks_version}" + +vpc: + id: ${vpc_ID} + subnets: + private: + PrivateSubnet01: + az: ${AWS_REGION}a + id: ${PrivateSubnet01} + PrivateSubnet02: + az: ${AWS_REGION}b + id: ${PrivateSubnet02} + PrivateSubnet03: + az: ${AWS_REGION}c + id: ${PrivateSubnet03} + +secretsEncryption: + keyARN: ${MASTER_ARN} + +fargateProfiles: + - name: eks-fp + selectors: + - namespace: ips + - namespace: kube-system + subnets: + - ${PrivateSubnet01} + - ${PrivateSubnet02} + - ${PrivateSubnet03} + +cloudWatch: + clusterLogging: + enableTypes: + ["api", "audit", "authenticator", "controllerManager", "scheduler"] + +EOF \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/scripts/kubectlLogsFromNamespace.sh b/doc/deployment-integration/aws-garnet/scripts/kubectlLogsFromNamespace.sh new file mode 100755 index 0000000..91c85d1 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/scripts/kubectlLogsFromNamespace.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +NAMESPACE="ips" + +# Create a directory for pod logs if it doesn't exist +mkdir -p ./podLogs + +# Associative array to store the last log timestamps for each pod +declare -A lastLogTimestamps + +while true; do + # Get the list of pods in the namespace + PODS=$(kubectl get pods -n $NAMESPACE --no-headers -o custom-columns=":metadata.name") + + for POD in $PODS; do + echo "Updating logs for pod: $POD" + + # Get the last timestamp we fetched logs up to + lastTimestamp="${lastLogTimestamps[$POD]}" + + # Use 'kubectl logs' with '--since-time' to get logs since the last retrieval + logs=$(kubectl logs -n $NAMESPACE $POD --since-time="$lastTimestamp") + + if [[ -z "$logs" ]]; then + echo "No new logs found for pod $POD" + else + # Append the new logs to the pod's log file + echo "$logs" >> "./podLogs/$POD.log" + echo "----------------------------------" >> "./podLogs/$POD.log" + + # Update the last log timestamp for this pod + lastLogTimestamps[$POD]=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + fi + done + + # Sleep for a few seconds before checking for new logs and pods again + sleep 3 +done diff --git a/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario1.png b/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario1.png new file mode 100644 index 0000000..a2b4f9d Binary files /dev/null and b/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario1.png differ diff --git a/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario2.png b/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario2.png new file mode 100644 index 0000000..dcc6f0c Binary files /dev/null and b/doc/deployment-integration/aws-garnet/static-assets/garnet-ds-connector-scenario2.png differ diff --git a/doc/deployment-integration/aws-garnet/yaml/aws-load-balancer-controller-service-account.yaml b/doc/deployment-integration/aws-garnet/yaml/aws-load-balancer-controller-service-account.yaml new file mode 100644 index 0000000..def69d5 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/yaml/aws-load-balancer-controller-service-account.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller + namespace: kube-system + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/AmazonEKSLoadBalancerControllerRole diff --git a/doc/deployment-integration/aws-garnet/yaml/eks-vpc-3az.yaml b/doc/deployment-integration/aws-garnet/yaml/eks-vpc-3az.yaml new file mode 100644 index 0000000..3fb6017 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/yaml/eks-vpc-3az.yaml @@ -0,0 +1,621 @@ +--- +AWSTemplateFormatVersion: "2010-09-09" +Description: "Amazon EKS Sample VPC - 3 AZ, Private 3 subnets, Public 3 subnets, 1 IGW, 3 NATGateways, Public RT, 3 Private RT, Security Group for ControlPlane " + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Worker Network Configuration" + Parameters: + - VpcBlock + - AvailabilityZoneA + - AvailabilityZoneB + - AvailabilityZoneC + - PublicSubnet01Block + - PublicSubnet02Block + - PublicSubnet03Block + - PrivateSubnet01Block + - PrivateSubnet02Block + - PrivateSubnet03Block + - TGWSubnet01Block + - TGWSubnet02Block + - TGWSubnet03Block + +Parameters: + VpcBlock: + Type: String + Default: 10.11.0.0/16 + Description: The CIDR range for the VPC. This should be a valid private (RFC 1918) CIDR range. + + AvailabilityZoneA: + Description: "Choose AZ1 for your VPC." + Type: AWS::EC2::AvailabilityZone::Name + Default: "eu-west-1a" + + AvailabilityZoneB: + Description: "Choose AZ2 for your VPC." + Type: AWS::EC2::AvailabilityZone::Name + Default: "eu-west-1b" + + AvailabilityZoneC: + Description: "Choose AZ1 for your VPC." + Type: AWS::EC2::AvailabilityZone::Name + Default: "eu-west-1c" + + PublicSubnet01Block: + Type: String + Default: 10.11.0.0/20 + Description: CidrBlock for public subnet 01 within the VPC + + PublicSubnet02Block: + Type: String + Default: 10.11.16.0/20 + Description: CidrBlock for public subnet 02 within the VPC + + PublicSubnet03Block: + Type: String + Default: 10.11.32.0/20 + Description: CidrBlock for public subnet 03 within the VPC + + PrivateSubnet01Block: + Type: String + Default: 10.11.48.0/20 + Description: CidrBlock for private subnet 01 within the VPC + + PrivateSubnet02Block: + Type: String + Default: 10.11.64.0/20 + Description: CidrBlock for private subnet 02 within the VPC + + PrivateSubnet03Block: + Type: String + Default: 10.11.80.0/20 + Description: CidrBlock for private subnet 03 within the VPC + + TGWSubnet01Block: + Type: String + Default: 10.11.251.0/24 + Description: CidrBlock for TGW subnet 01 within the VPC + + TGWSubnet02Block: + Type: String + Default: 10.11.252.0/24 + Description: CidrBlock for TGW subnet 02 within the VPC + + TGWSubnet03Block: + Type: String + Default: 10.11.253.0/24 + Description: CidrBlock for TGW subnet 03 within the VPC + +Resources: + ##################### + # Create-VPC : VPC # + ##################### + + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref VpcBlock + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + + ######################################################## + # Create-InternetGateway: + ######################################################## + + InternetGateway: + Type: "AWS::EC2::InternetGateway" + + ######################################################## + # Attach - VPC Gateway + ######################################################## + + VPCGatewayAttachment: + Type: "AWS::EC2::VPCGatewayAttachment" + Properties: + InternetGatewayId: !Ref InternetGateway + VpcId: !Ref VPC + + ######################################################## + # Create-Public-Subnet: PublicSubnet01,02,03,04 + ######################################################## + + PublicSubnet01: + Type: AWS::EC2::Subnet + Metadata: + Comment: Public Subnet 01 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnet01Block + AvailabilityZone: !Ref AvailabilityZoneA + MapPublicIpOnLaunch: "true" + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PublicSubnet01" + - Key: kubernetes.io/role/elb + Value: 1 + + PublicSubnet02: + Type: AWS::EC2::Subnet + Metadata: + Comment: Public Subnet 02 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnet02Block + AvailabilityZone: !Ref AvailabilityZoneB + MapPublicIpOnLaunch: "true" + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PublicSubnet02" + - Key: kubernetes.io/role/elb + Value: 1 + + PublicSubnet03: + Type: AWS::EC2::Subnet + Metadata: + Comment: Public Subnet 03 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PublicSubnet03Block + AvailabilityZone: !Ref AvailabilityZoneC + MapPublicIpOnLaunch: "true" + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PublicSubnet03" + - Key: kubernetes.io/role/elb + Value: 1 + + ##################################################################### + # Create-Public-RouteTable: + ##################################################################### + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: Public Subnets + - Key: Network + Value: PublicRT + + ################################################################################################ + # Associate-Public-RouteTable: VPC_Private_Subnet_a,b Accsociate VPC_Private_RouteTable # + ################################################################################################ + PublicSubnet01RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet01 + RouteTableId: !Ref PublicRouteTable + + PublicSubnet02RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet02 + RouteTableId: !Ref PublicRouteTable + + PublicSubnet03RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet03 + RouteTableId: !Ref PublicRouteTable + + ################################################################################################ + # Create Public Routing Table + ################################################################################################ + PublicRoute: + DependsOn: VPCGatewayAttachment + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + ################################################################################################ + # Create-NATGateway: NATGATEWAY01,02,03 + ################################################################################################ + NatGateway01: + DependsOn: + - NatGatewayEIP1 + - PublicSubnet01 + - VPCGatewayAttachment + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt "NatGatewayEIP1.AllocationId" + SubnetId: !Ref PublicSubnet01 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-NatGatewayAZ1" + + NatGateway02: + DependsOn: + - NatGatewayEIP2 + - PublicSubnet02 + - VPCGatewayAttachment + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt "NatGatewayEIP2.AllocationId" + SubnetId: !Ref PublicSubnet02 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-NatGatewayAZ2" + + NatGateway03: + DependsOn: + - NatGatewayEIP3 + - PublicSubnet03 + - VPCGatewayAttachment + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt "NatGatewayEIP3.AllocationId" + SubnetId: !Ref PublicSubnet03 + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-NatGatewayAZ3" + + NatGatewayEIP1: + DependsOn: + - VPCGatewayAttachment + Type: "AWS::EC2::EIP" + Properties: + Domain: vpc + + NatGatewayEIP2: + DependsOn: + - VPCGatewayAttachment + Type: "AWS::EC2::EIP" + Properties: + Domain: vpc + + NatGatewayEIP3: + DependsOn: + - VPCGatewayAttachment + Type: "AWS::EC2::EIP" + Properties: + Domain: vpc + + ######################################################## + # Create-Security-Group : ControlPlane + ######################################################## + ControlPlaneSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Cluster communication with worker nodes + VpcId: !Ref VPC + + ######################################################## + # Create-Security-Group : Session Manager + ######################################################## + SSMSG: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Open-up ports for HTTP/S from All network + GroupName: SSMSG + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + CidrIp: 0.0.0.0/0 + FromPort: "80" + ToPort: "80" + - IpProtocol: tcp + CidrIp: 0.0.0.0/0 + FromPort: "443" + ToPort: "443" + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-SSMSG" + ######################################################## + # Create-Private-Subnet: PrivateSubnet01,02 + ######################################################## + + PrivateSubnet01: + Type: AWS::EC2::Subnet + Metadata: + Comment: Private Subnet 01 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PrivateSubnet01Block + AvailabilityZone: !Ref AvailabilityZoneA + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PrivateSubnet01" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + PrivateSubnet02: + Type: AWS::EC2::Subnet + Metadata: + Comment: Private Subnet 02 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PrivateSubnet02Block + AvailabilityZone: !Ref AvailabilityZoneB + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PrivateSubnet02" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + PrivateSubnet03: + Type: AWS::EC2::Subnet + Metadata: + Comment: Private Subnet 03 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref PrivateSubnet03Block + AvailabilityZone: !Ref AvailabilityZoneC + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-PrivateSubnet03" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + ##################################################################### + # Create-Private-RouteTable: PrivateRT01,02 + ##################################################################### + PrivateRouteTable01: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: Private Subnet AZ1 + - Key: Network + Value: PrivateRT01 + + PrivateRouteTable02: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: Private Subnet AZ2 + - Key: Network + Value: PrivateRT02 + + PrivateRouteTable03: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: Private Subnet AZ3 + - Key: Network + Value: PrivateRT03 + + ################################################################################################ + # Associate-Private-RouteTable: VPC_Private_Subnet_a,b Accsociate VPC_Private_RouteTable # + ################################################################################################ + + PrivateSubnet01RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet01 + RouteTableId: !Ref PrivateRouteTable01 + + PrivateSubnet02RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet02 + RouteTableId: !Ref PrivateRouteTable02 + + PrivateSubnet03RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet03 + RouteTableId: !Ref PrivateRouteTable03 + + ################################################################################################ + # Add Prviate Routing Table + ################################################################################################ + + PrivateRoute01: + DependsOn: + - VPCGatewayAttachment + - NatGateway01 + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable01 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway01 + + PrivateRoute02: + DependsOn: + - VPCGatewayAttachment + - NatGateway02 + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable02 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway02 + + PrivateRoute03: + DependsOn: + - VPCGatewayAttachment + - NatGateway03 + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable03 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway03 + ######################################################## + # Create-TGW-Subnet: TGWSubnet01,02,03 + ######################################################## + + TGWSubnet01: + Type: AWS::EC2::Subnet + Metadata: + Comment: TGW Subnet 01 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref TGWSubnet01Block + AvailabilityZone: !Ref AvailabilityZoneA + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-TGWSubnet01" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + TGWSubnet02: + Type: AWS::EC2::Subnet + Metadata: + Comment: TGW Subnet 02 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref TGWSubnet02Block + AvailabilityZone: !Ref AvailabilityZoneB + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-TGWSubnet02" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + TGWSubnet03: + Type: AWS::EC2::Subnet + Metadata: + Comment: TGW Subnet 03 + Properties: + VpcId: !Ref VPC + CidrBlock: !Ref TGWSubnet03Block + AvailabilityZone: !Ref AvailabilityZoneC + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-TGWSubnet03" + - Key: kubernetes.io/role/internal-elb + Value: 1 + + ##################################################################### + # Create-TGW-RouteTable: TGWRT01,02,03,04 + ##################################################################### + TGWRouteTable01: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: TGW Subnet AZ1 + - Key: Network + Value: TGWRT01 + + TGWRouteTable02: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: TGW Subnet AZ2 + - Key: Network + Value: TGWRT02 + + TGWRouteTable03: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: TGW Subnet AZ3 + - Key: Network + Value: TGWRT03 + + ################################################################################################ + # Associate-TGW-RouteTable: VPC_TGW_Subnet_a,b Accsociate VPC_TGW_RouteTable # + ################################################################################################ + + TGWSubnet01RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref TGWSubnet01 + RouteTableId: !Ref TGWRouteTable01 + + TGWSubnet02RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref TGWSubnet02 + RouteTableId: !Ref TGWRouteTable02 + + TGWSubnet03RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref TGWSubnet03 + RouteTableId: !Ref TGWRouteTable03 + + ###################################################################### + # Create-System-Manager-Endpoint: Create VPC SystemManager Endpoint # + ###################################################################### + + SSMEndpoint: + Type: AWS::EC2::VPCEndpoint + Properties: + VpcId: !Ref VPC + ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm" + VpcEndpointType: Interface + PrivateDnsEnabled: True + SubnetIds: + - Ref: PrivateSubnet01 + - Ref: PrivateSubnet02 + - Ref: PrivateSubnet03 + SecurityGroupIds: + - Ref: SSMSG + + SSMMEndpoint: + Type: AWS::EC2::VPCEndpoint + Properties: + VpcId: !Ref VPC + ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages" + VpcEndpointType: Interface + PrivateDnsEnabled: True + SubnetIds: + - Ref: PrivateSubnet01 + - Ref: PrivateSubnet02 + - Ref: PrivateSubnet03 + SecurityGroupIds: + - Ref: SSMSG + +Outputs: + VpcId: + Description: The VPC Id + Value: !Ref VPC + + PublicSubnet01: + Description: PublicSubnet01 ID in the VPC + Value: !Ref PublicSubnet01 + + PublicSubnet02: + Description: PublicSubnet02 ID in the VPC + Value: !Ref PublicSubnet02 + + PublicSubnet03: + Description: PublicSubnet03 ID in the VPC + Value: !Ref PublicSubnet03 + + PrivateSubnet01: + Description: PrivateSubnet01 ID in the VPC + Value: !Ref PrivateSubnet01 + + PrivateSubnet02: + Description: PrivateSubnet02 ID in the VPC + Value: !Ref PrivateSubnet02 + + PrivateSubnet03: + Description: PrivateSubnet03 ID in the VPC + Value: !Ref PrivateSubnet03 + + SecurityGroups: + Description: Security group for the cluster control plane communication with worker nodes + Value: !Join [",", [!Ref ControlPlaneSecurityGroup]] + + TGWSubnet01: + Description: TGWSubnet01 ID in the VPC + Value: !Ref TGWSubnet01 + + TGWSubnet02: + Description: TGWSubnet02 ID in the VPC + Value: !Ref TGWSubnet02 + + TGWSubnet03: + Description: TGWSubnet03 ID in the VPC + Value: !Ref TGWSubnet03 \ No newline at end of file diff --git a/doc/deployment-integration/aws-garnet/yaml/nginx-ingress-controller.yaml b/doc/deployment-integration/aws-garnet/yaml/nginx-ingress-controller.yaml new file mode 100644 index 0000000..17bf569 --- /dev/null +++ b/doc/deployment-integration/aws-garnet/yaml/nginx-ingress-controller.yaml @@ -0,0 +1,653 @@ +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx + namespace: kube-system +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx + namespace: kube-system +rules: +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resourceNames: + - ingress-nginx-leader + resources: + - leases + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission + namespace: kube-system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission +rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: kube-system +--- +apiVersion: v1 +data: + allow-snippet-annotations: "true" + http-snippet: | + server { + listen 2443; + return 308 https://$host$request_uri; + } + proxy-real-ip-cidr: 10.11.0.0/16 + use-forwarded-headers: "true" +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-controller + namespace: kube-system +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60" + service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" + #service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX + service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https + service.beta.kubernetes.io/aws-load-balancer-type: nlb + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-controller + namespace: kube-system +spec: + externalTrafficPolicy: Local + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: tohttps + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: http + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: LoadBalancer +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-controller-admission + namespace: kube-system +spec: + ports: + - appProtocol: https + name: https-webhook + port: 443 + targetPort: webhook + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-controller + namespace: kube-system +spec: + minReadySeconds: 0 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + spec: + containers: + - args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + image: registry.k8s.io/ingress-nginx/controller:v1.8.1@sha256:e5c4824e7375fcf2a393e1c03c293b69759af37a9ca6abdb91b13d78a93da8bd + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 80 + name: https + protocol: TCP + - containerPort: 2443 + name: tohttps + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 90Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 + volumeMounts: + - mountPath: /usr/local/certificates/ + name: webhook-cert + readOnly: true + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission-create + namespace: kube-system +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission-create + spec: + containers: + - args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b + imagePullPolicy: IfNotPresent + name: create + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission-patch + namespace: kube-system +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission-patch + spec: + containers: + - args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b + imagePullPolicy: IfNotPresent + name: patch + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: nginx +spec: + controller: k8s.io/ingress-nginx +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.1 + name: ingress-nginx-admission +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: ingress-nginx-controller-admission + namespace: kube-system + path: /networking/v1/ingresses + failurePolicy: Fail + matchPolicy: Equivalent + name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None diff --git a/doc/deployment-integration/local-deployment/LOCAL.MD b/doc/deployment-integration/local-deployment/LOCAL.MD new file mode 100644 index 0000000..3db2093 --- /dev/null +++ b/doc/deployment-integration/local-deployment/LOCAL.MD @@ -0,0 +1,469 @@ +# Local Deployment + +In order to support development and exploration of the FIWARE Data Space Connector, a "Minimal Viable Dataspace" is +provided as part of this repo. + +## Quick Start + +> :warning: The local deployment uses [k3s](https://k3s.io/) and is currently only tested on linux. + +To start the Data Space, just use: + +```shell + mvn clean deploy -Plocal +``` + +Depending on the machine, it should take between 5 and 10min to spin up the complete data space. You can connect to the +running k3s-cluster via: + +```shell + export KUBECONFIG=$(pwd)/target/k3s.yaml + # get all deployed resources + kubectl get all --all-namespaces +``` + +## The Data Space + +![Overview](../../img/local/overview.jpg) + +The locally deployed Data Space consists of 2 Participants, connected through a Trust Anchor. + +### The Trust Anchor + +Every Data Spaces requires a framework that ensures trust between the participants. Depending on the requirements of the +concrete Data Space, +this can become a rather complex topic. Various trust-providers exist( +f.e. [Gaia-X Digital Clearing Houses](https://gaia-x.eu/gxdch)) and could be reused, as long as +they provide an implementation of +the [EBSI-Trusted Issuers Registry](https://hub.ebsi.eu/apis/pilot/trusted-issuers-registry/v4) to the participants. + +The local Data Spaces comes with the [FIWARE Trusted Issuers List](https://github.com/FIWARE/trusted-issuers-list) as a +rather simple implementation of that API, providing CRUD functionality +for Issuers and storage in an MySQL Database. After deployment, the API is available +at ```http://tir.127.0.0.1.nip.io:8080```. Both participants +are automatically registered as "Trusted Issuers" in the registry with their did's. + +Get a list of the issuers: + +```shell + curl -X GET http://tir.127.0.0.1.nip.io:8080/v4/issuers +``` + +A new issuer could for example be registered via: + +```shell + curl -X POST http://til.127.0.0.1.nip.io:8080/issuer \ + --header 'Content-Type: application/json' \ + --data '{ + "did": "did:key:myKey", + "credentials": [] + }' +``` + +For more information about the API, see +its [OpenAPI Spec](https://github.com/FIWARE/trusted-issuers-list/blob/main/api/trusted-issuers-list.yaml) + +## The Participants + +The minimal Data Space should provide an easy-to-understand introduction to the FIWARE Data Space. Therefor the roles of +the +participants are clearly seperated into "Data Consumer" and "Data Provider". However, in most real-world Data Spaces the +participants +will have both roles. They are not restricted to either consume or provide. + +In our scenario, the Data Provider(`M&P Operations Inc.`) is a company offering solutions to host and operate digital +services for other companies. The Data Consumer(`Fancy Marketplace Co.`) +provides a marketplace solution, listing offers from other companies. To fulfill their roles, they need different +components of the FIWARE Data Space Connector. + +### The Data Consumer + +![Consumer](../../img/local/consumer.jpg) + +Since the Data Consumer in our example is only retrieving data, it requires very few components: + +* [Keycloak](https://github.com/keycloak/keycloak) - to issue VerifiableCredentials +* [did-helper](https://github.com/wistefan/did-helper) - a small helper application, providing the decentralized + identity to be used for the local Data Space + +After deployment, Keycloak can be used to issue VerifiableCredentials for users or services, to be used for +authorization at other participants of the Data Space. +It comes with 2 preconfigured users: + +* the `keycloak-admin` - has a password generated during deployment, it can be retrieved + via ```kubectl get secret -n consumer -o json issuance-secret | jq '.data."keycloak-admin"' -r | base64 --decode``` +* the `test-user` - it has a fixed password, set to "test" + +The admin-console of keycloak is available at: ```http://keycloak-consumer.127.0.0.1.nip.io:8080```, login with +the `keycloak-admin` +The credentials issuance in the account-console is available +at: ```http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/account/oid4vci```, login with the `test-user` + +In order to retrieve an actual credential two ways are available: + +* Use the account-console and retrieve the credential with a wallet. Currently, we cannot recommend any for a local use + case. +* Get the credential via http-requests through the `SameDevice-Flow`: + +> :warning: The pre-authorized code and the offer expire within 30s for security reasons. Be fast. + +> :bulb: In case you did the demo before, you can use the following snippet to unset the env-vars: +> ```shell +> unset ACCESS_TOKEN; unset OFFER_URI; unset PRE_AUTHORIZED_CODE; \ +> unset CREDENTIAL_ACCESS_TOKEN; unset VERIFIABLE_CREDENTIAL; unset HOLDER_DID; \ +> unset VERIFIABLE_PRESENTATION; unset JWT_HEADER; unset PAYLOAD; unset SIGNATURE; unset JWT; \ +> unset VP_TOKEN; unset DATA_SERVICE_ACCESS_TOKEN; +> ``` + +Get an AccessToken from Keycloak: + +```shell + export ACCESS_TOKEN=$(curl -s -X POST http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/protocol/openid-connect/token \ + --header 'Accept: */*' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=password \ + --data client_id=admin-cli \ + --data username=test-user \ + --data password=test | jq '.access_token' -r); echo ${ACCESS_TOKEN} +``` + +(Optional, since in the local case we know all of the values in advance) +Get the credentials issuer information: + +```shell + curl -X GET http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/.well-known/openid-credential-issuer +``` + +Get a credential offer uri(for the `user-credential), using the retrieved AccessToken: + +```shell + export OFFER_URI=$(curl -s -X GET 'http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/protocol/oid4vc/credential-offer-uri?credential_configuration_id=user-credential' \ + --header "Authorization: Bearer ${ACCESS_TOKEN}" | jq '"\(.issuer)\(.nonce)"' -r); echo ${OFFER_URI} +``` + +Use the offer uri(e.g. the `issuer`and `nonce` fields), to retrieve the actual offer: + +```shell + export PRE_AUTHORIZED_CODE=$(curl -s -X GET ${OFFER_URI} \ + --header "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.grants."urn:ietf:params:oauth:grant-type:pre-authorized_code"."pre-authorized_code"' -r); echo ${PRE_AUTHORIZED_CODE} +``` + +Exchange the pre-authorized code from the offer with an AccessToken at the authorization server: + +```shell + export CREDENTIAL_ACCESS_TOKEN=$(curl -s -X POST http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/protocol/openid-connect/token \ + --header 'Accept: */*' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code \ + --data code=${PRE_AUTHORIZED_CODE} | jq '.access_token' -r); echo ${CREDENTIAL_ACCESS_TOKEN} +``` + +Use the returned access token to get the actual credential: + +```shell + export VERIFIABLE_CREDENTIAL=$(curl -s -X POST http://keycloak-consumer.127.0.0.1.nip.io:8080/realms/test-realm/protocol/oid4vc/credential \ + --header 'Accept: */*' \ + --header 'Content-Type: application/json' \ + --header "Authorization: Bearer ${CREDENTIAL_ACCESS_TOKEN}" \ + --data '{"credential_identifier":"user-credential", "format":"jwt_vc"}' | jq '.credential' -r); echo ${VERIFIABLE_CREDENTIAL} +``` + +You will receive a jwt-encoded credential to be used within the data space. + +### The Data Provider + +![Provider](../../img/local/provider.jpg) + +The Data Provider requires a couple of more components, in order to provide secure access to its data. It needs +essentially 3 building blocks: + +* Data-Service: In our example case, the data is provided by + an [NGSI-LD Context Broker](https://github.com/ScorpioBroker/ScorpioBroker) +* Authentication: + * [VCVerifier](https://github.com/FIWARE/VCVerifier): Verifies incoming VerifiableCredentials + through [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) and returns a JWT token + * [CredentialsConfigService](https://github.com/FIWARE/credentials-config-service): Allows to configure the Trusted + Lists to be used for certain credentials + * [TrustedIssuersList](https://github.com/FIWARE/trusted-issuers-list): Allows to specify the capabilities of + certain issuers, f.e. what credentials they are allowed to issue +* Authorization: + * [Apisix](https://apisix.apache.org/): An Api-Gateway that verifies the incoming JWT(provided by the verifier) and + acts as Policy Enforcement Point to authorize requests based on the PDP's decision + * [Open Policy Agent](https://www.openpolicyagent.org/): Acts as the Policy Decision Point, to evaluate existing + policies for requests and either allows or denies them. + * [ODRL-PAP](https://github.com/wistefan/odrl-pap): Policy Administration & Information Point, that allows to + configure policies in ODRL and provides them to the Open Policy Agent(translated into rego). It can be used to + offer additional infromation to be taken into account. +* [did-helper](https://github.com/wistefan/did-helper) - a small helper application, providing the decentralized + identity to be used for the local Data Space + +After the deployment, the provider can create a policy to allow access to its data. An example policy can be found in +the [test-resources](../../../it/src/test/resources/policies/energyReport.json) +It allows every participant to access entities of type ```EnergyReport```. + +> :warning: The PAP and Scorpio APIs are only published to make demo interactions easier. +> In real environments, they should never be public without any authentication/authorization framework in front of them. + +The policy can be created at the PAP via: + +```shell + curl -s -X 'POST' http://pap-provider.127.0.0.1.nip.io:8080/policy \ + -H 'Content-Type: application/json' \ + -d '{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "dct": "http://purl.org/dc/terms/", + "owl": "http://www.w3.org/2002/07/owl#", + "odrl": "http://www.w3.org/ns/odrl/2/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "skos": "http://www.w3.org/2004/02/skos/core#" + }, + "@id": "https://mp-operation.org/policy/common/type", + "@type": "odrl:Policy", + "odrl:permission": { + "odrl:assigner": { + "@id": "https://www.mp-operation.org/" + }, + "odrl:target": { + "@type": "odrl:AssetCollection", + "odrl:source": "urn:asset", + "odrl:refinement": [ + { + "@type": "odrl:Constraint", + "odrl:leftOperand": "ngsi-ld:entityType", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "EnergyReport" + } + ] + }, + "odrl:assignee": { + "@id": "odrl:any" + }, + "odrl:action": { + "@id": "odrl:read" + } + } + }' +``` + +Data can be created through the NGSI-LD API itself. In order to make interaction easier, its directly available through +an ingress at ```http://scorpio-provider.127.0.0.1.nip.io/ngsi-ld/v1```. In +real environments, no endpoint should be publicly available without beeing protected by the authorization framework. +Create an entity via: + +```shell + curl -s -X POST http://scorpio-provider.127.0.0.1.nip.io:8080/ngsi-ld/v1/entities \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": "urn:ngsi-ld:EnergyReport:fms-1", + "type": "EnergyReport", + "name": { + "type": "Property", + "value": "Standard Server" + }, + "consumption": { + "type": "Property", + "value": "94" + } + }' +``` + +## Demo Interactions + +Once everything is deployed and configured(e.g. the consumer received a credential - +see [The Data Consumer](#the-data-consumer) - and policy/entity are setup - see [The Data Provider](#the-data-provider)) +, +the consumer can access the data as following: + +### Authenticate via OID4VP + +> :warning: Those steps assume that interaction with consumer and provider already happend, e.g. a VerifiableCredential +> is available +> and policy/entity are created. + +The credential needs to be presented for authentication +through [OID4VP]((https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). +Every required information for that flow can be retrieved via the standard endpoints. + +If you try to request the provider api without authentication, you will receive an 401: + +```shell + curl -s -X GET 'http://mp-data-service.127.0.0.1.nip.io:8080/ngsi-ld/v1/entities/urn:ngsi-ld:EnergyReport:fms-1' +``` + +The normal flow is now to request the oidc-information at the well-known endpoint: + +```shell + export TOKEN_ENDPOINT=$(curl -s -X GET 'http://mp-data-service.127.0.0.1.nip.io:8080/.well-known/openid-configuration' | jq -r '.token_endpoint'); echo $TOKEN_ENDPOINT +``` + +In the response, the grant type `vp_token` will be present, indicating the support for the OID4VP authentication flow: + +```json +{ + "issuer": "http://provider-verifier.127.0.0.1.nip.io:8080", + "authorization_endpoint": "http://provider-verifier.127.0.0.1.nip.io:8080", + "token_endpoint": "http://provider-verifier.127.0.0.1.nip.io:8080/services/data-service/token", + "jwks_uri": "http://provider-verifier.127.0.0.1.nip.io:8080/.well-known/jwks", + "scopes_supported": [ + "default" + ], + "response_types_supported": [ + "token" + ], + "response_mode_supported": [ + "direct_post" + ], + "grant_types_supported": [ + "authorization_code", + "vp_token" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "EdDSA", + "ES256" + ] +} +``` + +With that information, the authentication flow at the verifier(e.g.`"http://provider-verifier.127.0.0.1.nip.io:8080`) +can be started. +First, the credential needs to be encoded into a vp_token. If you want to do that manually, first a did and the +corresponding key-material is required. +You can create such via: + +```shell + docker run -v $(pwd):/cert quay.io/wi_stefan/did-helper:0.1.1 +``` + +This will produce the files cert.pem, cert.pfx, private-key.pem, public-key.pem and did.json, containing all required +information for the generated did:key. +Find the did here: + +```shell + export HOLDER_DID=$(cat did.json | jq '.id' -r); echo ${HOLDER_DID} +``` + +As a next step, a VerifiablePresentation, containing the Credential has to be created: + +```shell + export VERIFIABLE_PRESENTATION="{ + \"@context\": [\"https://www.w3.org/2018/credentials/v1\"], + \"type\": [\"VerifiablePresentation\"], + \"verifiableCredential\": [ + \"${VERIFIABLE_CREDENTIAL}\" + ], + \"holder\": \"${HOLDER_DID}\" + }"; echo ${VERIFIABLE_PRESENTATION} +``` + +Now, the presentation has to be embedded into a signed JWT: + +Setup the header: + +```shell + export JWT_HEADER=$(echo -n "{\"alg\":\"ES256\", \"typ\":\"JWT\", \"kid\":\"${HOLDER_DID}\"}"| base64 -w0 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//); echo Header: ${JWT_HEADER} +``` + +Setup the payload: + +```shell + export PAYLOAD=$(echo -n "{\"iss\": \"${HOLDER_DID}\", \"sub\": \"${HOLDER_DID}\", \"vp\": ${VERIFIABLE_PRESENTATION}}" | base64 -w0 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$//); echo Payload: ${PAYLOAD}; +``` + +Create the signature: + +```shell + export SIGNATURE=$(echo -n "${JWT_HEADER}.${PAYLOAD}" | openssl dgst -sha256 -binary -sign private-key.pem | base64 -w0 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//); echo Signature: ${SIGNATURE}; +``` + +Combine them to the JWT: + +```shell + export JWT="${JWT_HEADER}.${PAYLOAD}.${SIGNATURE}"; echo The Token: ${JWT} +``` + +The JWT representation of the JWT has to be Base64-encoded(no padding!): + +```shell + export VP_TOKEN=$(echo -n ${JWT} | base64 -w0 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//); echo ${VP_TOKEN} +``` + +The vp_token can then be exchanged for the access-token + +```shell + export DATA_SERVICE_ACCESS_TOKEN=$(curl -s -X POST $TOKEN_ENDPOINT \ + --header 'Accept: */*' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=vp_token \ + --data vp_token=${VP_TOKEN} \ + --data scope=default | jq '.access_token' -r ); echo ${DATA_SERVICE_ACCESS_TOKEN} +``` + +With that token, try to access the data again: + +```shell + curl -s -X GET 'http://mp-data-service.127.0.0.1.nip.io:8080/ngsi-ld/v1/entities/urn:ngsi-ld:EnergyReport:fms-1' \ + --header 'Accept: application/json' \ + --header "Authorization: Bearer ${DATA_SERVICE_ACCESS_TOKEN}" +``` + +## Deployment details + +In order to make the setup properly working locally and usable for development and try out, some adaptions have been +made and will be explained here. + +### Ingress + +To have the local environment as close to reality as possible, all interaction happens +through [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/). The ingress is provided via the +[Traefik-IngressController](https://doc.traefik.io/traefik/providers/kubernetes-ingress/) and configured +here: [k3s/infra/traefik]. Additionally, to ensure access to public endpoints happens +equally from inside the cluster and outside of it, [CoreDNS](https://coredns.io/)(deployed on default with k3s) is +instructed to resolve the ingresses(e.g. *.127.0.0.1.nip.io) directly to the loadbalancer-service of Traefik. + +Available ingresses: + +| URL | Component | Participant | Comment | Only for Demo | +|-------------------------------------------------|----------------------------------------------------------------------------|--------------|-------------------------------------------------------------------------------------|------------------------------------------------------| +| http://tir.127.0.0.1.nip.io:8080/ | [Trusted Issuers Registry](https://github.com/FIWARE/trusted-issuers-list) | Trust Anchor | Provides the list of trusted issuers | no | +| http://til.127.0.0.1.nip.io:8080/ | [Trusted Issuers List API](https://github.com/FIWARE/trusted-issuers-list) | Trust Anchor | Create,Update,Delete functionality for the Trusted Issuers Registry | should not be publicly available in real data spaces | +| http://keycloak-consumer.127.0.0.1.nip.io:8080/ | [Keycloak](https://github.com/keycloak/keycloak) | Consumer | Issues credentials on behalf of the consumer | no | +| http://did-consumer.127.0.0.1.nip.io:8080/ | [did-helper](https://github.com/wistefan/did-helper) | Consumer | Helper to provide access to the consumers did and key material | yes, should never be public | +| http://mp-data-service.127.0.0.1.nip.io:8080/ | [Apisix](https://apisix.apache.org/) | Provider | ApiGateway to be used as entry point to all secured services, f.e. the data-service | no | +| http://provider-verifier.127.0.0.1.nip.io:8080/ | [VCVerifier](https://github.com/FIWARE/VCVerifier) | Provider | Authentication endpoint, used for authenticating through VerifiableCredentials | no | +| http://did-provider.127.0.0.1.nip.io:8080/ | [did-helper](https://github.com/wistefan/did-helper) | Provider | Helper to provide access to the providers did and key material | yes, should never be public | +| http://scorpio-provider.127.0.0.1.nip.io:8080/ | [Scorpio ContextBroker](https://github.com/ScorpioBroker/ScorpioBroker) | Provider | Provides direct access to the context broker, to be used for test setup. | yes, should only be available through the PEP | +| http://pap-provider.127.0.0.1.nip.io:8080/ | [ODRL-PAP](https://github.com/wistefan/odrl-pap) | Provider | Allows configuration of the access policie, used for authorization | yes, should only be available to authorized users | + + +### Participant Identity + +In a Data Space, every participant requires an identity. The FIWARE Data Space relies on [Decentralized Identifiers](https://www.w3.org/TR/did-core/) +to identify its participants. While the concrete scheme to be used for a Data Space needs to be decided by its requirements, +the local installation uses [did:key](https://w3c-ccg.github.io/did-method-key/). While the did's are not well readable for humans, +they are well-supported, can be resolved without any external interaction and can easily be generated within the deployment. That makes +them a perfect fit for the local use-case. +All participants(e.g. the consumer and the participant) get a did generated on installation, by using the [did-helper](https://github.com/wistefan/did-helper). +The identities and connected key-material is automatically distributed in the cluster and set in the componets that require it. + +In real world data spaces, the participants should rather use stabled identities, which can be [did:key](https://w3c-ccg.github.io/did-method-key/), but also +more organization-focused once like [did:web](https://w3c-ccg.github.io/did-method-web/) or [did:elsi](https://alastria.github.io/did-method-elsi/). + +### Deployment + +The deployment leverages the [k3s-maven-plugin](https://github.com/kokuwaio/k3s-maven-plugin) to stay as close to the real deployments as possible, +while providing integration with the [integration tests](../../../it/src/test). +In order to build a concrete deployment, [maven](https://maven.apache.org/) executes the following steps, that would also be done by a `normal` deployment process: + +1. Copy the required charts([charts/](../../../charts)) to the target folder, e.g. `target/charts` +2. Copy additionally required resources([k3s/infra & k3s/namespaces](../../../k3s/)) to the target folder, e.g. `target/k3s` +3. Execute `helm template` on the charts, with the local values provided for each participant(e.g. [trust-anchor](../../../k3s/trust-anchor.yaml), [provider](../../../k3s/provider.yaml) and [consumer](../../../k3s/consumer.yaml)) and copy the manifests to the target folder(e.g. `target/k3s`) +4. Spin up the cluster +5. Apply the infrastructure resources to the cluster, via `kubectl apply` +6. Apply the charts to the cluster, via `kubectl apply` diff --git a/doc/flows/contract-management/README.md b/doc/flows/contract-management/README.md index 1a67781..0f2dac9 100644 --- a/doc/flows/contract-management/README.md +++ b/doc/flows/contract-management/README.md @@ -1,5 +1,7 @@ # Contract Management +> :construction: Please note, that this document is currently being reworked for the usage of ODRL policies and does not contain the latest information + With the [tmforum-api](https://github.com/FIWARE/tmforum-api) and the [contract-management](https://github.com/FIWARE/contract-management) notification listener, the FIWARE Data Space Connector provides components to perform contract management based on the TMForum APIs. @@ -47,7 +49,7 @@ In order to get the credentials, use the [HappyPets Keycloak](https://happypets- > :construction: The [Business API Ecosystem](https://github.com/FIWARE-TMForum/Business-API-Ecosystem) as a Marketplace solution build on top of the TMForum-APIs is currently beeing updated to the latest TMForum-APIs and the security framework. Once its finished, it can be used a Marketplace, including UI, for the Data Space Connector. -To provide a working example for Contract Management, using the Data Space Connector, a [postman colletion is provided](../../../examples/tmf/). +To provide a working example for Contract Management, using the Data Space Connector, a [postman colletion is provided](./tmf/). It includes the following steps of the aquisition process: > :bulb: Since frontend-solutions are still under construction, plain REST-calls are used for the flow. Since all calls require a valid JWT, the [demo-portal](https://packetdelivery-portal.dsba.fiware.dev/) for the provider has a link to get a plain token in exchange for the Verifiable Credential. Log-in either as CONSUMER or PROVIDER (see [Credentials and Role-Assignment](#credentials-and-role-assignment)) to get tokens. diff --git a/examples/tmf/README.md b/doc/flows/contract-management/tmf/README.md similarity index 100% rename from examples/tmf/README.md rename to doc/flows/contract-management/tmf/README.md diff --git a/examples/tmf/tmf.postman_collection.json b/doc/flows/contract-management/tmf/tmf.postman_collection.json similarity index 100% rename from examples/tmf/tmf.postman_collection.json rename to doc/flows/contract-management/tmf/tmf.postman_collection.json diff --git a/doc/img/Connector_Components.png b/doc/img/flows/Connector_Components.png similarity index 100% rename from doc/img/Connector_Components.png rename to doc/img/flows/Connector_Components.png diff --git a/doc/img/Flows_Consumer-Registration.png b/doc/img/flows/Flows_Consumer-Registration.png similarity index 100% rename from doc/img/Flows_Consumer-Registration.png rename to doc/img/flows/Flows_Consumer-Registration.png diff --git a/doc/img/Flows_Contract-Management.png b/doc/img/flows/Flows_Contract-Management.png similarity index 100% rename from doc/img/Flows_Contract-Management.png rename to doc/img/flows/Flows_Contract-Management.png diff --git a/doc/img/Flows_Interaction-H2M.png b/doc/img/flows/Flows_Interaction-H2M.png similarity index 100% rename from doc/img/Flows_Interaction-H2M.png rename to doc/img/flows/Flows_Interaction-H2M.png diff --git a/doc/img/Flows_Interaction-M2M.png b/doc/img/flows/Flows_Interaction-M2M.png similarity index 100% rename from doc/img/Flows_Interaction-M2M.png rename to doc/img/flows/Flows_Interaction-M2M.png diff --git a/doc/img/Flows_Onboarding.png b/doc/img/flows/Flows_Onboarding.png similarity index 100% rename from doc/img/Flows_Onboarding.png rename to doc/img/flows/Flows_Onboarding.png diff --git a/doc/img/local/components.drawio b/doc/img/local/components.drawio new file mode 100644 index 0000000..91ca379 --- /dev/null +++ b/doc/img/local/components.drawio @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/local/components.png b/doc/img/local/components.png new file mode 100644 index 0000000..b7caf0a Binary files /dev/null and b/doc/img/local/components.png differ diff --git a/doc/img/local/consumer.drawio b/doc/img/local/consumer.drawio new file mode 100644 index 0000000..1bcb04b --- /dev/null +++ b/doc/img/local/consumer.drawio @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/local/consumer.jpg b/doc/img/local/consumer.jpg new file mode 100644 index 0000000..6fae3c7 Binary files /dev/null and b/doc/img/local/consumer.jpg differ diff --git a/doc/img/local/overview.drawio b/doc/img/local/overview.drawio new file mode 100644 index 0000000..41638b6 --- /dev/null +++ b/doc/img/local/overview.drawio @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/local/overview.jpg b/doc/img/local/overview.jpg new file mode 100644 index 0000000..f5e140d Binary files /dev/null and b/doc/img/local/overview.jpg differ diff --git a/doc/img/local/provider.drawio b/doc/img/local/provider.drawio new file mode 100644 index 0000000..72172f2 --- /dev/null +++ b/doc/img/local/provider.drawio @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/local/provider.jpg b/doc/img/local/provider.jpg new file mode 100644 index 0000000..9872363 Binary files /dev/null and b/doc/img/local/provider.jpg differ diff --git a/it/pom.xml b/it/pom.xml new file mode 100644 index 0000000..08e4b96 --- /dev/null +++ b/it/pom.xml @@ -0,0 +1,275 @@ + + + 4.0.0 + + org.fiware.dataspace + it + jar + + + connector + org.fiware.dataspace + 0.0.1 + + + + ${project.parent.basedir} + 1.18.32 + 2.0.6 + 2.0.1.Final + 2.14.2 + 1.3.2 + 4.2.0 + 5.9.2 + 7.11.1 + 24.0.4 + + + + + + + org.junit + junit-bom + ${version.org.junit.bom} + pom + import + + + io.cucumber + cucumber-bom + ${version.io.cucumber} + pom + import + + + + + + jitpack.io + https://jitpack.io + + + + + + org.projectlombok + lombok + ${version.org.projectlombok} + + + org.slf4j + slf4j-api + ${version.org.slf4j.slf4j-api} + + + javax.validation + validation-api + ${version.javax.validation} + + + com.fasterxml.jackson.core + jackson-annotations + ${version.com.fasterxml.jackson.core} + + + com.squareup.okhttp + okhttp + 2.7.5 + + + + + com.github.multiformats + java-multibase + v1.1.1 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + org.awaitility + awaitility + ${version.org.awaitility} + test + + + io.cucumber + cucumber-java + test + + + io.cucumber + cucumber-junit-platform-engine + test + + + org.keycloak + keycloak-admin-client + ${version.org.keycloak} + test + + + org.keycloak + keycloak-core + ${version.org.keycloak} + test + + + org.keycloak + keycloak-crypto-default + ${version.org.keycloak} + test + + + org.keycloak + keycloak-services + ${version.org.keycloak} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${jdk.version} + ${release.version} + + + org.projectlombok + lombok + ${version.org.projectlombok} + + + + + + + + + + local + + + + maven-resources-plugin + + true + + + + io.kokuwa.maven + helm-maven-plugin + + true + + + + io.kokuwa.maven + k3s-maven-plugin + + true + + + + + + + test + + + + org.apache.maven.plugins + maven-jar-plugin + ${version.org.apache.maven.plugins.maven-jar} + + true + + + + maven-resources-plugin + + false + + + + copy-resources-trust-anchor + validate + + + copy-resources-namespaces + validate + + copy-resources + + + + copy-resources-dsc + validate + + copy-resources + + + + + + io.kokuwa.maven + helm-maven-plugin + + false + + + + template-dsc-consumer + test-compile + + + template-dsc-provider + test-compile + + + template-trust-anchor + test-compile + + + + + io.kokuwa.maven + k3s-maven-plugin + + false + + + + apply-participants + pre-integration-test + + + create-namespaces + pre-integration-test + + + + + + + + \ No newline at end of file diff --git a/it/src/test/java/org/fiware/dataspace/it/components/FancyMarketplaceEnvironment.java b/it/src/test/java/org/fiware/dataspace/it/components/FancyMarketplaceEnvironment.java new file mode 100644 index 0000000..e455f85 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/FancyMarketplaceEnvironment.java @@ -0,0 +1,22 @@ +package org.fiware.dataspace.it.components; + +/** + * @author Stefan Wiedemann + */ +public abstract class FancyMarketplaceEnvironment { + public static final String DID_CONSUMER_ADDRESS = "http://did-consumer.127.0.0.1.nip.io:8080"; + public static final String CONSUMER_KEYCLOAK_ADDRESS = "http://keycloak-consumer.127.0.0.1.nip.io:8080"; + + public static final String OIDC_WELL_KNOWN_PATH = "/.well-known/openid-configuration"; + private static final String TEST_REALM = "test-realm"; + private static final String TEST_USER_NAME = "test-user"; + private static final String TEST_USER_PASSWORD = "test"; + + /** + * Returns an access token to be used with Keycloak. + */ + public static String loginToConsumerKeycloak() { + KeycloakHelper consumerKeycloak = new KeycloakHelper(TEST_REALM, CONSUMER_KEYCLOAK_ADDRESS); + return consumerKeycloak.getUserToken(TEST_USER_NAME, TEST_USER_PASSWORD); + } +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/KeycloakHelper.java b/it/src/test/java/org/fiware/dataspace/it/components/KeycloakHelper.java new file mode 100644 index 0000000..66b903a --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/KeycloakHelper.java @@ -0,0 +1,32 @@ +package org.fiware.dataspace.it.components; + +import lombok.RequiredArgsConstructor; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.token.TokenManager; +import org.keycloak.representations.idm.ClientRepresentation; + +import java.util.List; + +/** + * @author Stefan Wiedemann + */ +@RequiredArgsConstructor +public class KeycloakHelper { + private final String realm; + private final String address; + + public String getUserToken(String username, String password) { + + TokenManager tokenManager = KeycloakBuilder.builder() + .username(username) + .password(password) + .realm(realm) + .grantType("password") + .clientId("admin-cli") + .serverUrl(address) + .build() + .tokenManager(); + return tokenManager.getAccessToken().getToken(); + } +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/MPOperationsEnvironment.java b/it/src/test/java/org/fiware/dataspace/it/components/MPOperationsEnvironment.java new file mode 100644 index 0000000..5c00413 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/MPOperationsEnvironment.java @@ -0,0 +1,36 @@ +package org.fiware.dataspace.it.components; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import org.apache.http.HttpStatus; +import org.fiware.dataspace.it.components.model.OpenIdConfiguration; + +import static org.fiware.dataspace.it.components.TestUtils.OBJECT_MAPPER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Stefan Wiedemann + */ +public abstract class MPOperationsEnvironment { + + public static final String DID_PROVIDER_ADDRESS = "http://did-provider.127.0.0.1.nip.io:8080"; + public static final String PROVIDER_PAP_ADDRESS = "http://pap-provider.127.0.0.1.nip.io:8080"; + public static final String PROVIDER_API_ADDRESS = "http://mp-data-service.127.0.0.1.nip.io:8080"; + public static final String SCORPIO_ADDRESS = "http://scorpio-provider.127.0.0.1.nip.io:8080"; + + public static final String OIDC_WELL_KNOWN_PATH = "/.well-known/openid-configuration"; + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + public static OpenIdConfiguration getOpenIDConfiguration() throws Exception { + Request wellKnownRequest = new Request.Builder().get() + .url(PROVIDER_API_ADDRESS + OIDC_WELL_KNOWN_PATH) + .build(); + Response wellKnownResponse = HTTP_CLIENT.newCall(wellKnownRequest).execute(); + assertEquals(HttpStatus.SC_OK, wellKnownResponse.code(), "The oidc config should have been returned."); + return OBJECT_MAPPER.readValue(wellKnownResponse.body().string(), OpenIdConfiguration.class); + } + +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/RunCucumberTest.java b/it/src/test/java/org/fiware/dataspace/it/components/RunCucumberTest.java new file mode 100644 index 0000000..ff2c2a2 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/RunCucumberTest.java @@ -0,0 +1,18 @@ +package org.fiware.dataspace.it.components; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; + +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +/** + * @author Stefan Wiedemann + */ +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("it") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") +public class RunCucumberTest { +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/StepDefinitions.java b/it/src/test/java/org/fiware/dataspace/it/components/StepDefinitions.java new file mode 100644 index 0000000..650f456 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/StepDefinitions.java @@ -0,0 +1,181 @@ +package org.fiware.dataspace.it.components; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; +import io.cucumber.java.After; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; +import org.awaitility.Awaitility; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.fiware.dataspace.it.components.model.OpenIdConfiguration; +import org.keycloak.common.crypto.CryptoIntegration; + +import java.io.IOException; +import java.io.InputStream; +import java.security.Security; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Stefan Wiedemann + */ +@Slf4j +public class StepDefinitions { + + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String USER_CREDENTIAL = "user-credential"; + private static final String GRANT_TYPE_VP_TOKEN = "vp_token"; + private static final String RESPONSE_TYPE_DIRECT_POST = "direct_post"; + + private Wallet fancyMarketplaceEmployeeWallet; + + private List createdPolicies = new ArrayList<>(); + private List createdEntities = new ArrayList<>(); + + @Before + public void setup() throws Exception { + CryptoIntegration.init(this.getClass().getClassLoader()); + Security.addProvider(new BouncyCastleProvider()); + fancyMarketplaceEmployeeWallet = new Wallet(); + } + + @After + public void cleanUp() { + cleanUpPolicies(); + cleanUpEntities(); + } + + private void cleanUpPolicies() { + createdPolicies.forEach(policyId -> { + Request deletionRequest = new Request.Builder() + .url(MPOperationsEnvironment.PROVIDER_PAP_ADDRESS + "/policy/" + policyId) + .delete() + .build(); + try { + HTTP_CLIENT.newCall(deletionRequest).execute(); + } catch (IOException e) { + // just log + log.warn("Was not able to clean up policy {}.", policyId); + } + }); + } + + private void cleanUpEntities() { + createdEntities.forEach(entityId -> { + Request deletionRequest = new Request.Builder() + .url(MPOperationsEnvironment.SCORPIO_ADDRESS + "/ngsi-ld/v1/entities/" + entityId) + .delete() + .build(); + try { + HTTP_CLIENT.newCall(deletionRequest).execute(); + } catch (IOException e) { + // just log + log.warn("Was not able to clean up entitiy {}.", entityId); + } + }); + } + + @Given("M&P Operations is registered as a participant in the data space.") + public void checkMPRegistered() throws Exception { + Request didCheckRequest = new Request.Builder() + .url(TrustAnchorEnvironment.TIR_ADDRESS + "/v4/issuers/" + getDid(MPOperationsEnvironment.DID_PROVIDER_ADDRESS)) + .build(); + assertEquals(HttpStatus.SC_OK, HTTP_CLIENT.newCall(didCheckRequest).execute().code(), "The did should be registered at the trust-anchor."); + } + + + @Given("Fancy Marketplace is registered as a participant in the data space.") + public void checkFMRegistered() throws Exception { + Request didCheckRequest = new Request.Builder() + .url(TrustAnchorEnvironment.TIR_ADDRESS + "/v4/issuers/" + getDid(FancyMarketplaceEnvironment.DID_CONSUMER_ADDRESS)) + .build(); + assertEquals(HttpStatus.SC_OK, HTTP_CLIENT.newCall(didCheckRequest).execute().code(), "The did should be registered at the trust-anchor."); + } + + @When("M&P Operations registers a policy to allow every participant access to its energy reports.") + public void mpRegisterEnergyReportPolicy() throws Exception { + RequestBody policyBody = RequestBody.create(MediaType.parse("application/json"), getPolicy("energyReport")); + Request policyCreationRequest = new Request.Builder() + .post(policyBody) + .url(MPOperationsEnvironment.PROVIDER_PAP_ADDRESS + "/policy") + .build(); + Response policyCreationResponse = HTTP_CLIENT.newCall(policyCreationRequest).execute(); + assertEquals(HttpStatus.SC_OK, policyCreationResponse.code(), "The policy should have been created."); + createdPolicies.add(policyCreationResponse.header("Location")); + } + + @When("M&P Operations creates an energy report.") + public void createEnergyReport() throws Exception { + Map offerEntity = Map.of("type", "EnergyReport", + "id", "urn:ngsi-ld:EnergyReport:fms-1", + "name", Map.of("type", "Property", "value", "Standard Server"), + "consumption", Map.of("type", "Property", "value", "94")); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), OBJECT_MAPPER.writeValueAsString(offerEntity)); + + Request creationRequest = new Request.Builder() + .url(MPOperationsEnvironment.SCORPIO_ADDRESS + "/ngsi-ld/v1/entities") + .post(requestBody) + .build(); + assertEquals(HttpStatus.SC_CREATED, HTTP_CLIENT.newCall(creationRequest).execute().code(), "The entity should have been created."); + createdEntities.add("urn:ngsi-ld:EnergyReport:fms-1"); + } + + @When("Fancy Marketplace issues a credential to its employee.") + public void issueCredentialToEmployee() throws Exception { + String accessToken = FancyMarketplaceEnvironment.loginToConsumerKeycloak(); + fancyMarketplaceEmployeeWallet.getCredentialFromIssuer(accessToken, FancyMarketplaceEnvironment.CONSUMER_KEYCLOAK_ADDRESS, USER_CREDENTIAL); + } + + @Then("Fancy Marketplace' employee can access the EnergyReport.") + public void accessTheEnergyReport() throws Exception { + OpenIdConfiguration openIdConfiguration = MPOperationsEnvironment.getOpenIDConfiguration(); + assertTrue(openIdConfiguration.getGrantTypesSupported().contains(GRANT_TYPE_VP_TOKEN), "The M&P environment should support vp_tokens"); + assertTrue(openIdConfiguration.getResponseModeSupported().contains(RESPONSE_TYPE_DIRECT_POST), "The M&P environment should support direct_post"); + assertNotNull(openIdConfiguration.getTokenEndpoint(), "The M&P environment should provide a token endpoint."); + + String accessToken = fancyMarketplaceEmployeeWallet.exchangeCredentialForToken(openIdConfiguration, USER_CREDENTIAL); + Request authenticatedEntityRequest = new Request.Builder().get() + .url(MPOperationsEnvironment.PROVIDER_API_ADDRESS + "/ngsi-ld/v1/entities/urn:ngsi-ld:EnergyReport:fms-1") + .addHeader("Authorization", "Bearer " + accessToken) + .addHeader("Accept", "application/json") + .build(); + + Awaitility.await() + .atMost(Duration.ofSeconds(60)) + .until(() -> { + return HttpStatus.SC_OK == HTTP_CLIENT.newCall(authenticatedEntityRequest).execute().code(); + }); + } + + private String getDid(String didHelperAddress) throws Exception { + Request didRequest = new Request.Builder().url(didHelperAddress + "/did-material/did.env").build(); + Response didResponse = HTTP_CLIENT.newCall(didRequest).execute(); + String didEnv = didResponse.body().string(); + return didEnv.split("=")[1]; + } + + private String getPolicy(String policyName) throws IOException { + InputStream policyInputStream = this.getClass().getResourceAsStream(String.format("/policies/%s.json", policyName)); + StringBuilder sb = new StringBuilder(); + for (int ch; (ch = policyInputStream.read()) != -1; ) { + sb.append((char) ch); + } + return sb.toString(); + } + +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/TestUtils.java b/it/src/test/java/org/fiware/dataspace/it/components/TestUtils.java new file mode 100644 index 0000000..4b76c5b --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/TestUtils.java @@ -0,0 +1,127 @@ +package org.fiware.dataspace.it.components; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.java.sl.In; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.checkerframework.checker.units.qual.C; +import org.keycloak.common.util.KeystoreUtil; +import org.keycloak.common.util.PemUtils; +import org.keycloak.crypto.KeyWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Map; + +/** + * Helper methods for the test. + */ +public class TestUtils { + + private static final String CONSUMER_DID_HELPER = "http://did-consumer.127.0.0.1.nip.io:8080"; + private static final String PROVIDER_DID_HELPER = "http://did-provider.127.0.0.1.nip.io:8080"; + private static final String PRIVATE_KEY_PATH = "/did-material/private-key.pem"; + private static final String DID_ENV_PATH = "/did-material/did.env"; + + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + private static final HttpClient HTTP_CLIENT = HttpClient + .newBuilder() + // we don´t follow the redirect directly, since we are not a real wallet + .followRedirects(HttpClient.Redirect.NEVER) + .build(); + + private TestUtils() { + // prevent instantiation + } + + public static String getFormDataAsString(Map formData) { + StringBuilder formBodyBuilder = new StringBuilder(); + for (Map.Entry singleEntry : formData.entrySet()) { + if (formBodyBuilder.length() > 0) { + formBodyBuilder.append("&"); + } + formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8)); + formBodyBuilder.append("="); + formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8)); + } + return formBodyBuilder.toString(); + } + + public static String getConsumerDid() throws Exception { + return getDid(CONSUMER_DID_HELPER); + } + + public static String getProviderDid() throws Exception { + return getDid(PROVIDER_DID_HELPER); + } + + private static String getDid(String host) throws Exception { + HttpRequest didRequest = HttpRequest.newBuilder() + .uri(URI.create(host + DID_ENV_PATH)) + .GET() + .build(); + HttpResponse didResponse = HTTP_CLIENT.send(didRequest, HttpResponse.BodyHandlers.ofInputStream()); + return getDidFromEnv(didResponse.body()); + } + + private static String getDidFromEnv(InputStream didInputStream) throws Exception { + String didEnvString = new String(didInputStream.readAllBytes(), StandardCharsets.UTF_8); + return didEnvString.split("=")[1]; + } + + private static KeyWrapper getKey(String host) throws Exception { + HttpRequest keyRequest = HttpRequest.newBuilder() + .uri(URI.create(host + PRIVATE_KEY_PATH)) + .GET() + .build(); + HttpResponse keyResponse = HTTP_CLIENT.send(keyRequest, HttpResponse.BodyHandlers.ofInputStream()); + return getKeyWrapper(keyResponse.body()); + } + + public static KeyWrapper getConsumerKey() throws Exception { + return getKey(CONSUMER_DID_HELPER); + } + + public static KeyWrapper getProviderKey() throws Exception { + return getKey(PROVIDER_DID_HELPER); + } + + private static KeyWrapper getKeyWrapper(InputStream keyInputStream) throws Exception { + String keyString = new String(keyInputStream.readAllBytes(), StandardCharsets.UTF_8); + PEMParser pemParser = new PEMParser(new StringReader(keyString)); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + Object object = pemParser.readObject(); + KeyPair kp = converter.getKeyPair((PEMKeyPair) object); + PrivateKey privateKey = kp.getPrivate(); + KeyWrapper keyWrapper = new KeyWrapper(); + keyWrapper.setPrivateKey(privateKey); + return keyWrapper; + } +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/TrustAnchorEnvironment.java b/it/src/test/java/org/fiware/dataspace/it/components/TrustAnchorEnvironment.java new file mode 100644 index 0000000..2dc80f9 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/TrustAnchorEnvironment.java @@ -0,0 +1,9 @@ +package org.fiware.dataspace.it.components; + +/** + * @author Stefan Wiedemann + */ +public abstract class TrustAnchorEnvironment { + + public static final String TIR_ADDRESS = "http://tir.127.0.0.1.nip.io:8080"; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/Wallet.java b/it/src/test/java/org/fiware/dataspace/it/components/Wallet.java new file mode 100644 index 0000000..d8ffcf6 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/Wallet.java @@ -0,0 +1,295 @@ +package org.fiware.dataspace.it.components; + +import com.squareup.okhttp.FormEncodingBuilder; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; +import io.ipfs.multibase.Multibase; +import jakarta.ws.rs.core.MediaType; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; +import org.bouncycastle.jce.interfaces.ECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.fiware.dataspace.it.components.model.Credential; +import org.fiware.dataspace.it.components.model.CredentialOffer; +import org.fiware.dataspace.it.components.model.CredentialRequest; +import org.fiware.dataspace.it.components.model.Grant; +import org.fiware.dataspace.it.components.model.IssuerConfiguration; +import org.fiware.dataspace.it.components.model.OfferUri; +import org.fiware.dataspace.it.components.model.OpenIdConfiguration; +import org.fiware.dataspace.it.components.model.SupportedConfiguration; +import org.fiware.dataspace.it.components.model.TokenResponse; +import org.fiware.dataspace.it.components.model.VerifiablePresentation; +import org.keycloak.crypto.ECDSASignatureSignerContext; +import org.keycloak.crypto.KeyUse; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.representations.JsonWebToken; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.time.Clock; +import java.util.Base64; +import java.util.HashMap; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.fiware.dataspace.it.components.TestUtils.OBJECT_MAPPER; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Stefan Wiedemann + */ +@Slf4j +public class Wallet { + + private static final String OPENID_CREDENTIAL_ISSUER_PATH = "/realms/test-realm/.well-known/openid-credential-issuer"; + private static final String CREDENTIAL_OFFER_URI_PATH = "/realms/test-realm/protocol/oid4vc/credential-offer-uri"; + private static final String OID_WELL_KNOWN_PATH = "/.well-known/openid-configuration"; + private static final String PRE_AUTHORIZED_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; + + private final Map credentialStorage = new HashMap<>(); + + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + + private final KeyWrapper walletKey; + private final String did; + + public Wallet() throws Exception { + walletKey = getECKey(); + did = walletKey.getKid(); + } + + + public String exchangeCredentialForToken(OpenIdConfiguration openIdConfiguration, String credentialId) throws Exception { + String vpToken = Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(createVPToken(did, walletKey, credentialStorage.get(credentialId)).getBytes()); + RequestBody requestBody = new FormEncodingBuilder() + .add("grant_type", "vp_token") + .add("vp_token", vpToken) + .add("scope", "default") + .build(); + Request tokenRequest = new Request.Builder() + .post(requestBody) + .url(openIdConfiguration.getTokenEndpoint()) + .build(); + Response tokenResponse = HTTP_CLIENT.newCall(tokenRequest).execute(); + + assertEquals(HttpStatus.SC_OK, tokenResponse.code(), "A token should have been responded."); + + TokenResponse accessTokenResponse = OBJECT_MAPPER.readValue(tokenResponse.body().string(), TokenResponse.class); + assertNotNull(accessTokenResponse.getAccessToken(), "The access token should have been returned."); + return accessTokenResponse.getAccessToken(); + } + + private String createVPToken(String did, KeyWrapper key, String credential) { + VerifiablePresentation verifiablePresentation = new VerifiablePresentation(); + verifiablePresentation.setVerifiableCredential(List.of(credential)); + verifiablePresentation.setHolder(did); + key.setKid(did); + key.setAlgorithm("ES256"); + key.setUse(KeyUse.SIG); + + ECDSASignatureSignerContext signerContext = new ECDSASignatureSignerContext(key); + + JsonWebToken jwt = new JsonWebToken() + .issuer(did) + .subject(did) + .iat(Clock.systemUTC().millis()); + jwt.setOtherClaims("vp", verifiablePresentation); + return new JWSBuilder() + .type("JWT") + .jsonContent(jwt) + .sign(signerContext); + } + + + public void getCredentialFromIssuer(String userToken, String issuerHost, String credentialId) throws Exception { + IssuerConfiguration issuerConfiguration = getIssuerConfiguration(issuerHost); + OfferUri offerUri = getCredentialOfferUri(userToken, issuerHost, credentialId); + CredentialOffer credentialOffer = getCredentialOffer(userToken, offerUri); + + var theCredential = getCredential(issuerConfiguration, credentialOffer); + credentialStorage.put(credentialId, theCredential); + } + + + public IssuerConfiguration getIssuerConfiguration(String issuerHost) throws Exception { + + Request configRequest = new Request.Builder() + .get() + .url(issuerHost + OPENID_CREDENTIAL_ISSUER_PATH) + .build(); + Response configResponse = HTTP_CLIENT.newCall(configRequest).execute(); + + assertEquals(HttpStatus.SC_OK, configResponse.code(), "An issuer config should have been returned."); + return OBJECT_MAPPER.readValue(configResponse.body().string(), IssuerConfiguration.class); + } + + public OfferUri getCredentialOfferUri(String keycloakJwt, String issuerHost, String credentialConfigId) throws Exception { + + Request request = new Request.Builder() + .url(issuerHost + CREDENTIAL_OFFER_URI_PATH + "?credential_configuration_id=" + credentialConfigId) + .get() + .header("Authorization", "Bearer " + keycloakJwt) + .build(); + + Response uriResponse = HTTP_CLIENT.newCall(request).execute(); + + assertEquals(HttpStatus.SC_OK, uriResponse.code(), "An uri should have been returned."); + return OBJECT_MAPPER.readValue(uriResponse.body().string(), OfferUri.class); + } + + public CredentialOffer getCredentialOffer(String keycloakJwt, OfferUri offerUri) throws Exception { + + Request uriRequest = new Request.Builder() + .get() + .url(offerUri.getIssuer() + offerUri.getNonce()) + .header("Authorization", "Bearer " + keycloakJwt) + .build(); + + Response offerResponse = HTTP_CLIENT.newCall(uriRequest).execute(); + + assertEquals(HttpStatus.SC_OK, offerResponse.code(), "An offer should have been returned."); + return OBJECT_MAPPER.readValue(offerResponse.body().string(), CredentialOffer.class); + } + + public String getTokenForOffer(IssuerConfiguration issuerConfiguration, CredentialOffer credentialOffer) throws Exception { + String authorizationServer = issuerConfiguration.getAuthorizationServers().get(0); + OpenIdConfiguration openIdConfiguration = getOpenIdConfiguration(authorizationServer); + assertTrue(openIdConfiguration.getGrantTypesSupported().contains(PRE_AUTHORIZED_GRANT_TYPE), "The grant type should actually be supported by the authorization server."); + + Grant preAuthorizedGrant = credentialOffer.getGrants().get(PRE_AUTHORIZED_GRANT_TYPE); + return getAccessToken(openIdConfiguration.getTokenEndpoint(), preAuthorizedGrant.getPreAuthorizedCode()); + } + + public String getCredential(IssuerConfiguration issuerConfiguration, CredentialOffer credentialOffer) throws Exception { + String accessToken = getTokenForOffer(issuerConfiguration, credentialOffer); + + String credentialResponse = credentialOffer.getCredentialConfigurationIds() + .stream() + .map(offeredCredentialId -> issuerConfiguration.getCredentialConfigurationsSupported().get(offeredCredentialId)) + .map(supportedCredential -> { + try { + return requestOffer(accessToken, issuerConfiguration.getCredentialEndpoint(), supportedCredential); + } catch (Exception e) { + return null; + } + }) + .filter(Objects::nonNull) + .findFirst() + .get(); + return OBJECT_MAPPER.readValue(credentialResponse, Credential.class).getCredential(); + } + + private String requestOffer(String token, String credentialEndpoint, SupportedConfiguration offeredCredential) throws Exception { + CredentialRequest credentialRequest = new CredentialRequest(); + credentialRequest.setCredentialIdentifier(offeredCredential.getId()); + credentialRequest.setFormat(offeredCredential.getFormat()); + + RequestBody credentialRequestBody = RequestBody + .create( + com.squareup.okhttp.MediaType.parse(MediaType.APPLICATION_JSON), + OBJECT_MAPPER.writeValueAsString(credentialRequest)); + Request credentialHttpRequest = new Request.Builder() + .post(credentialRequestBody) + .url(credentialEndpoint) + .header("Authorization", "Bearer " + token) + .header("Content-Type", MediaType.APPLICATION_JSON) + .build(); + + + Response credentialResponse = HTTP_CLIENT.newCall(credentialHttpRequest).execute(); + assertEquals(HttpStatus.SC_OK, credentialResponse.code(), "A credential should have been returned."); + return credentialResponse.body().string(); + } + + public String getAccessToken(String tokenEndpoint, String preAuthorizedCode) throws Exception { + RequestBody requestBody = new FormEncodingBuilder() + .add("grant_type", PRE_AUTHORIZED_GRANT_TYPE) + .add("code", preAuthorizedCode) + .build(); + Request tokenRequest = new Request.Builder() + .url(tokenEndpoint) + .post(requestBody) + .build(); + + + Response tokenResponse = HTTP_CLIENT.newCall(tokenRequest).execute(); + assertEquals(HttpStatus.SC_OK, tokenResponse.code(), "A valid token should have been returned."); + + return OBJECT_MAPPER.readValue(tokenResponse.body().string(), TokenResponse.class).getAccessToken(); + } + + public OpenIdConfiguration getOpenIdConfiguration(String authorizationServer) throws Exception { + Request request = new Request.Builder() + .get() + .url(authorizationServer + OID_WELL_KNOWN_PATH) + .build(); + Response openIdConfigResponse = HTTP_CLIENT.newCall(request).execute(); + + assertEquals(HttpStatus.SC_OK, openIdConfigResponse.code(), "An openId config should have been returned."); + return OBJECT_MAPPER.readValue(openIdConfigResponse.body().string(), OpenIdConfiguration.class); + } + + + private static KeyWrapper getECKey() throws Exception { + try { + Security.addProvider(new BouncyCastleProvider()); + + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC"); + ECGenParameterSpec spec = new ECGenParameterSpec("P-256"); + kpGen.initialize(spec); + + var keyPair = kpGen.generateKeyPair(); + KeyWrapper kw = new KeyWrapper(); + kw.setPrivateKey(keyPair.getPrivate()); + kw.setPublicKey(keyPair.getPublic()); + kw.setUse(KeyUse.SIG); + kw.setKid(generateDid(keyPair)); + kw.setType("EC"); + kw.setAlgorithm("ES256"); + return kw; + + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new RuntimeException(e); + } + } + + private static String generateDid(KeyPair keyPair) throws Exception { + if (keyPair.getPublic() instanceof ECPublicKey ecPublicKey) { + byte[] encodedQ = ecPublicKey.getQ().getEncoded(true); + byte[] codecBytes = new byte[2]; + codecBytes[0] = HexFormat.of().parseHex("80")[0]; + codecBytes[1] = HexFormat.of().parseHex("24")[0]; + + byte[] prefixed = new byte[encodedQ.length + codecBytes.length]; + System.arraycopy(codecBytes, 0, prefixed, 0, codecBytes.length); + System.arraycopy(encodedQ, 0, prefixed, codecBytes.length, encodedQ.length); + + String encodedKeyRaw = Multibase.encode(Multibase.Base.Base58BTC, prefixed); + return "did:key:" + encodedKeyRaw; + } + throw new IllegalArgumentException("Key pair is not supported."); + } + + private static byte[] marshalCompressed(BigInteger x, BigInteger y) { + // we only support the P-256 curve here, thus a fixed length can be set + byte[] compressed = new byte[33]; + compressed[0] = (byte) (y.testBit(0) ? 1 : 0); + System.arraycopy(x.toByteArray(), 0, compressed, 1, x.toByteArray().length); + return compressed; + } + +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/Credential.java b/it/src/test/java/org/fiware/dataspace/it/components/model/Credential.java new file mode 100644 index 0000000..e2b1378 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/Credential.java @@ -0,0 +1,18 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Credential { + + private String credential; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialOffer.java b/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialOffer.java new file mode 100644 index 0000000..0127aa3 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialOffer.java @@ -0,0 +1,29 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CredentialOffer { + + @JsonProperty("grants") + private Map grants; + + @JsonProperty("credential_issuer") + private String credentialIssuer; + + @JsonProperty("credential_configuration_ids") + private List credentialConfigurationIds; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialRequest.java b/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialRequest.java new file mode 100644 index 0000000..15afd4a --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/CredentialRequest.java @@ -0,0 +1,22 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CredentialRequest { + + private Format format; + + @JsonProperty("credential_identifier") + private String credentialIdentifier; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/Format.java b/it/src/test/java/org/fiware/dataspace/it/components/model/Format.java new file mode 100644 index 0000000..e947716 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/Format.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Enum of supported credential formats + * + * @author Stefan Wiedemann + */ +public enum Format { + + /** + * LD-Credentials {@see https://www.w3.org/TR/vc-data-model/} + */ + LDP_VC("ldp_vc"), + + /** + * JWT-Credentials {@see https://identity.foundation/jwt-vc-presentation-profile/} + */ + JWT_VC("jwt_vc"), + + /** + * SD-JWT-Credentials {@see https://drafts.oauth.net/oauth-sd-jwt-vc/draft-ietf-oauth-sd-jwt-vc.html} + */ + SD_JWT_VC("sd-jwt_vc"); + + private String value; + + Format(String value) { + this.value = value; + } + + /** + * Convert a String into String, as specified in the + * See JAX RS 2.0 Specification, section 3.2, p. 12 + */ + public static Format fromString(String s) { + for (Format b : Format.values()) { + // using Objects.toString() to be safe if value type non-object type + // because types like 'int' etc. will be auto-boxed + if (java.util.Objects.toString(b.value).equals(s)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected string value '" + s + "'"); + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static Format fromValue(String value) { + for (Format b : Format.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } +} \ No newline at end of file diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/Grant.java b/it/src/test/java/org/fiware/dataspace/it/components/model/Grant.java new file mode 100644 index 0000000..4052d3b --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/Grant.java @@ -0,0 +1,20 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Grant { + + @JsonProperty("pre-authorized_code") + private String preAuthorizedCode; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/IssuerConfiguration.java b/it/src/test/java/org/fiware/dataspace/it/components/model/IssuerConfiguration.java new file mode 100644 index 0000000..c348b97 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/IssuerConfiguration.java @@ -0,0 +1,32 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class IssuerConfiguration { + + @JsonProperty("credential_endpoint") + private String credentialEndpoint; + + @JsonProperty("credential_issuer") + private String credentialIssuer; + + @JsonProperty("authorization_servers") + private List authorizationServers; + + @JsonProperty("credential_configurations_supported") + private Map credentialConfigurationsSupported; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/OfferUri.java b/it/src/test/java/org/fiware/dataspace/it/components/model/OfferUri.java new file mode 100644 index 0000000..887d522 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/OfferUri.java @@ -0,0 +1,19 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class OfferUri { + + private String issuer; + private String nonce; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/OpenIdConfiguration.java b/it/src/test/java/org/fiware/dataspace/it/components/model/OpenIdConfiguration.java new file mode 100644 index 0000000..e21b330 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/OpenIdConfiguration.java @@ -0,0 +1,40 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * OID Configuration, that maps only those fields we need for the pre-authorized flow + * + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class OpenIdConfiguration { + + @JsonProperty("token_endpoint") + private String tokenEndpoint; + + @JsonProperty("authorization_endpoint") + private String authorizationEndpoint; + + @JsonProperty("jwks_uri") + private String jwksUri; + + @JsonProperty("scopes_supported") + private List scopesSupported; + + @JsonProperty("response_mode_supported") + private List responseModeSupported; + + @JsonProperty("grant_types_supported") + private List grantTypesSupported; + +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/SupportedConfiguration.java b/it/src/test/java/org/fiware/dataspace/it/components/model/SupportedConfiguration.java new file mode 100644 index 0000000..910699e --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/SupportedConfiguration.java @@ -0,0 +1,26 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SupportedConfiguration { + + @JsonProperty("id") + private String id; + + @JsonProperty("format") + private Format format; + + @JsonProperty("scope") + private String scope; +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/TokenResponse.java b/it/src/test/java/org/fiware/dataspace/it/components/model/TokenResponse.java new file mode 100644 index 0000000..dd2f249 --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/TokenResponse.java @@ -0,0 +1,25 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TokenResponse { + + @JsonProperty("access_token") + private String accessToken; + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("expires_in") + private long expiresIn; + +} diff --git a/it/src/test/java/org/fiware/dataspace/it/components/model/VerifiablePresentation.java b/it/src/test/java/org/fiware/dataspace/it/components/model/VerifiablePresentation.java new file mode 100644 index 0000000..5912eed --- /dev/null +++ b/it/src/test/java/org/fiware/dataspace/it/components/model/VerifiablePresentation.java @@ -0,0 +1,26 @@ +package org.fiware.dataspace.it.components.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author Stefan Wiedemann + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class VerifiablePresentation { + + private List type = List.of("VerifiablePresentation"); + private List verifiableCredential; + private String holder; + @JsonProperty("@context") + private List context = List.of("https://www.w3.org/2018/credentials/v1"); + +} diff --git a/it/src/test/resources/it/mvds_basic.feature b/it/src/test/resources/it/mvds_basic.feature new file mode 100644 index 0000000..84825d1 --- /dev/null +++ b/it/src/test/resources/it/mvds_basic.feature @@ -0,0 +1,9 @@ +Feature: The Data Space should support a basic data exchange between registered participants. + + Scenario: A registered consumer can retrieve data from a registered data provider. + Given M&P Operations is registered as a participant in the data space. + And Fancy Marketplace is registered as a participant in the data space. + When M&P Operations registers a policy to allow every participant access to its energy reports. + And M&P Operations creates an energy report. + And Fancy Marketplace issues a credential to its employee. + Then Fancy Marketplace' employee can access the EnergyReport. \ No newline at end of file diff --git a/it/src/test/resources/policies/energyReport.json b/it/src/test/resources/policies/energyReport.json new file mode 100644 index 0000000..eb1c4e6 --- /dev/null +++ b/it/src/test/resources/policies/energyReport.json @@ -0,0 +1,37 @@ +{ + "@context": { + "dc": "http://purl.org/dc/elements/1.1/", + "dct": "http://purl.org/dc/terms/", + "owl": "http://www.w3.org/2002/07/owl#", + "odrl": "http://www.w3.org/ns/odrl/2/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "skos": "http://www.w3.org/2004/02/skos/core#" + }, + "@id": "https://mp-operation.org/policy/common/type", + "@type": "odrl:Policy", + "odrl:permission": { + "odrl:assigner": { + "@id": "https://www.mp-operation.org/" + }, + "odrl:target": { + "@type": "odrl:AssetCollection", + "odrl:source": "urn:asset", + "odrl:refinement": [ + { + "@type": "odrl:Constraint", + "odrl:leftOperand": "ngsi-ld:entityType", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "EnergyReport" + } + ] + }, + "odrl:assignee": { + "@id": "odrl:any" + }, + "odrl:action": { + "@id": "odrl:read" + } + } +} diff --git a/k3s/consumer.yaml b/k3s/consumer.yaml new file mode 100644 index 0000000..9acb910 --- /dev/null +++ b/k3s/consumer.yaml @@ -0,0 +1,281 @@ +vcverifier: + enabled: false +credentials-config-service: + enabled: false +trusted-issuers-list: + enabled: false +mysql: + enabled: false +odrl-pap: + enabled: false +apisix: + enabled: false +scorpio: + enabled: false +postgis: + enabled: false + + +postgresql: + primary: + persistence: + enabled: false + readReplicas: + persistence: + enabled: false + +keycloak: + ingress: + enabled: true + hostname: keycloak-consumer.127.0.0.1.nip.io + args: + - -ec + - | + #!/bin/sh + export $(cat /did-material/did.env) + export KC_HOSTNAME=keycloak-consumer.127.0.0.1.nip.io + env | grep DID + /opt/keycloak/bin/kc.sh start --features oid4vc-vci --import-realm + initContainers: + - name: read-only-workaround + image: quay.io/wi_stefan/keycloak:25.0.0-PRE + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + cp -r /opt/keycloak/lib/quarkus/* /quarkus + volumeMounts: + - name: empty-dir + mountPath: /quarkus + subPath: app-quarkus-dir + + - name: get-did + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + apt-get -y update; apt-get -y install wget; apt-get -y install sed; + + cd /did-material + wget http://did-helper:3001/did-material/cert.pfx + wget http://did-helper:3001/did-material/did.env + mkdir -p /did-material/client + cd /did-material/client + wget http://did-helper.provider.svc.cluster.local:3002/did-material/did.env + sed -i -e 's/DID/CLIENT_DID/g' /did-material/client/did.env + echo "" >> /did-material/did.env + echo $(cat /did-material/client/did.env) >> /did-material/did.env + echo $(cat /did-material/did.env) + volumeMounts: + - name: did-material + mountPath: /did-material + + - name: register-at-tir + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + source /did-material/did.env + apt-get -y update; apt-get -y install curl + curl -X 'POST' 'http://tir.trust-anchor.svc.cluster.local:8080/issuer' -H 'Content-Type: application/json' -d "{\"did\": \"${DID}\", \"credentials\": []}" + volumeMounts: + - name: did-material + mountPath: /did-material + + - name: register-at-til + image: quay.io/curl/curl:8.1.2 + command: + - /bin/sh + args: + - -ec + - | + #!/bin/sh + export $(cat /did-material/did.env) + /bin/init.sh + volumeMounts: + - name: consumer-til-registration + mountPath: /bin/init.sh + subPath: init.sh + - name: did-material + mountPath: /did-material + + extraVolumes: + - name: did-material + emptyDir: { } + - name: qtm-temp + emptyDir: { } + - name: realms + configMap: + name: test-realm-realm + - name: consumer-til-registration + configMap: + name: consumer-til-registration + defaultMode: 0755 + realm: + frontendUrl: http://keycloak-consumer.127.0.0.1.nip.io:8080 + import: true + name: test-realm + clientRoles: | + "${CLIENT_DID}": [ + { + "name": "READER", + "description": "Is allowed to register", + "clientRole": true + }, + { + "name": "WRITER", + "description": "Is allowed to see", + "clientRole": true + } + ] + + users: | + { + "username": "test-user", + "enabled": true, + "email": "test@user.org", + "firstName": "Test", + "lastName": "Reader", + "credentials": [ + { + "type": "password", + "value": "test" + } + ], + "clientRoles": { + "${CLIENT_DID}": [ + "READER" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "groups": [ + ] + } + clients: | + { + "clientId": "${CLIENT_DID}", + "enabled": true, + "description": "Client to connect test.org", + "surrogateAuthRequired": false, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "defaultRoles": [], + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "oid4vc", + "attributes": { + "client.secret.creation.time": "1675260539", + "vc.user-credential.format": "jwt_vc", + "vc.user-credential.scope": "UserCredential", + "vc.verifiable-credential.format": "jwt_vc", + "vc.verifiable-credential.scope": "VerifiableCredential" + }, + "protocolMappers": [ + { + "name": "target-role-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-target-role-mapper", + "config": { + "subjectProperty": "roles", + "clientId": "${CLIENT_DID}", + "supportedCredentialTypes": "VerifiableCredential" + } + }, + { + "name": "target-vc-role-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-target-role-mapper", + "config": { + "subjectProperty": "roles", + "clientId": "${CLIENT_DID}", + "supportedCredentialTypes": "UserCredential" + } + }, + { + "name": "context-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-context-mapper", + "config": { + "context": "https://www.w3.org/2018/credentials/v1", + "supportedCredentialTypes": "VerifiableCredential,UserCredential" + } + }, + { + "name": "email-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-user-attribute-mapper", + "config": { + "subjectProperty": "email", + "userAttribute": "email", + "supportedCredentialTypes": "UserCredential" + } + }, + { + "name": "firstName-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-user-attribute-mapper", + "config": { + "subjectProperty": "firstName", + "userAttribute": "firstName", + "supportedCredentialTypes": "UserCredential" + } + }, + { + "name": "lastName-mapper", + "protocol": "oid4vc", + "protocolMapper": "oid4vc-user-attribute-mapper", + "config": { + "subjectProperty": "lastName", + "userAttribute": "lastName", + "supportedCredentialTypes": "UserCredential" + } + } + ], + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + } +did: + enabled: true + secret: issuance-secret + serviceType: ClusterIP + port: 3001 + cert: + country: BE + state: BRUSSELS + locality: Brussels + organization: Fancy Marketplace Co. + commonName: www.fancy-marketplace.biz + ingress: + enabled: true + host: did-consumer.127.0.0.1.nip.io + +# register the consumer at the til. Only possible if it runs in the same environment and recommendable for demo deployments +registration: + enabled: true + configMap: consumer-til-registration + til: http://trusted-issuers-list.provider.svc.cluster.local:8080 + did: ${DID} + credentialsType: UserCredential diff --git a/k3s/infra/coredns/coredns-cm.yaml b/k3s/infra/coredns/coredns-cm.yaml new file mode 100644 index 0000000..66539b0 --- /dev/null +++ b/k3s/infra/coredns/coredns-cm.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system +data: + Corefile: | + .:53 { + errors + health + ready + file /etc/coredns/nip.db 127.0.0.1.nip.io + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + } + hosts /etc/coredns/NodeHosts { + ttl 60 + reload 15s + fallthrough + } + prometheus :9153 + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + import /etc/coredns/custom/*.override + } + import /etc/coredns/custom/*.server + NodeHosts: | + 172.17.0.2 k3s + # in order to make the nip.io served local host addresses also available cluster internal(e.g. to the pods), we instruct coredns + # to forward all such requests inside the cluster to the traefik' loadbalancer-service + nip.db: | + 127.0.0.1.nip.io. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600 + 127.0.0.1.nip.io. IN NS a.iana-servers.net. + 127.0.0.1.nip.io. IN NS b.iana-servers.net. + *.127.0.0.1.nip.io. IN CNAME traefik-loadbalancer.infra.svc.cluster.local. \ No newline at end of file diff --git a/k3s/infra/coredns/deployment.yaml b/k3s/infra/coredns/deployment.yaml new file mode 100644 index 0000000..35f23ca --- /dev/null +++ b/k3s/infra/coredns/deployment.yaml @@ -0,0 +1,128 @@ +# overwrites the k3s default coredns deployment, in order to load the additional configuration +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + k8s-app: kube-dns + kubernetes.io/name: CoreDNS + name: coredns + namespace: kube-system +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 0 + selector: + matchLabels: + k8s-app: kube-dns + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + k8s-app: kube-dns + spec: + containers: + - args: + - -conf + - /etc/coredns/Corefile + image: rancher/mirrored-coredns-coredns:1.10.1 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: coredns + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 9153 + name: metrics + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /ready + port: 8181 + scheme: HTTP + periodSeconds: 2 + successThreshold: 1 + timeoutSeconds: 1 + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_BIND_SERVICE + drop: + - all + readOnlyRootFilesystem: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /etc/coredns + name: config-volume + readOnly: true + - mountPath: /etc/coredns/custom + name: custom-config-volume + readOnly: true + dnsPolicy: Default + nodeSelector: + kubernetes.io/os: linux + priorityClassName: system-cluster-critical + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: coredns + serviceAccountName: coredns + terminationGracePeriodSeconds: 30 + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + topologySpreadConstraints: + - labelSelector: + matchLabels: + k8s-app: kube-dns + maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + volumes: + - configMap: + defaultMode: 420 + items: + - key: Corefile + path: Corefile + - key: NodeHosts + path: NodeHosts + - key: nip.db + path: nip.db + name: coredns + name: config-volume + - configMap: + defaultMode: 420 + name: coredns-custom + optional: true + name: custom-config-volume \ No newline at end of file diff --git a/k3s/infra/traefik/deployment.yaml b/k3s/infra/traefik/deployment.yaml new file mode 100644 index 0000000..4190b3d --- /dev/null +++ b/k3s/infra/traefik/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: traefik + namespace: infra + labels: + app.kubernetes.io/name: traefik +spec: + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: traefik + template: + metadata: + labels: + app.kubernetes.io/name: traefik + spec: + containers: + - name: traefik + image: traefik:v2.6.0 + imagePullPolicy: IfNotPresent + args: + - --providers.kubernetesingress=true + - --entrypoints.traefik.address=:8090 + - --entrypoints.http.address=:8080 + - --accesslog=true + - --accesslog.fields.defaultmode=keep + - --accesslog.fields.headers.defaultmode=keep + - --ping=true + - --api.insecure=true + - --api.dashboard=true + - --api.debug=true + - --global.checknewversion=false + - --global.sendAnonymousUsage=false + ports: + - name: http + containerPort: 8080 + - name: admin + containerPort: 8090 + startupProbe: + httpGet: + path: /ping + port: admin + initialDelaySeconds: 1 + periodSeconds: 1 + successThreshold: 1 + failureThreshold: 60 + readinessProbe: + httpGet: + path: /ping + port: admin + livenessProbe: + httpGet: + path: /ping + port: admin + securityContext: + runAsUser: 10001 + runAsGroup: 10001 + readOnlyRootFilesystem: true + serviceAccountName: traefik + terminationGracePeriodSeconds: 0 diff --git a/k3s/infra/traefik/ingress.yaml b/k3s/infra/traefik/ingress.yaml new file mode 100644 index 0000000..3b9e7d2 --- /dev/null +++ b/k3s/infra/traefik/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: traefik + namespace: infra + labels: + app.kubernetes.io/name: traefik +spec: + rules: + - host: traefik.127.0.0.1.nip.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: traefik + port: + name: admin diff --git a/k3s/infra/traefik/rbac.yaml b/k3s/infra/traefik/rbac.yaml new file mode 100644 index 0000000..11d109c --- /dev/null +++ b/k3s/infra/traefik/rbac.yaml @@ -0,0 +1,57 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: traefik + namespace: infra + labels: + app.kubernetes.io/name: traefik +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: traefik + namespace: infra + labels: + app.kubernetes.io/name: traefik +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: traefik + namespace: infra + labels: + app.kubernetes.io/name: traefik +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: traefik +subjects: + - name: traefik + namespace: infra + kind: ServiceAccount diff --git a/k3s/infra/traefik/service.yaml b/k3s/infra/traefik/service.yaml new file mode 100644 index 0000000..8c9131f --- /dev/null +++ b/k3s/infra/traefik/service.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Service +metadata: + name: traefik-admin + namespace: infra + labels: + app.kubernetes.io/name: traefik +spec: + selector: + app.kubernetes.io/name: traefik + ports: + - name: admin + port: 80 + targetPort: admin +--- +apiVersion: v1 +kind: Service +metadata: + name: traefik-loadbalancer + namespace: infra + labels: + app.kubernetes.io/name: traefik +spec: + type: LoadBalancer + selector: + app.kubernetes.io/name: traefik + ports: + - name: http + port: 8080 + targetPort: http diff --git a/k3s/namespaces/consumer.yaml b/k3s/namespaces/consumer.yaml new file mode 100644 index 0000000..f4661a3 --- /dev/null +++ b/k3s/namespaces/consumer.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: consumer \ No newline at end of file diff --git a/k3s/namespaces/infra.yaml b/k3s/namespaces/infra.yaml new file mode 100644 index 0000000..d48ab19 --- /dev/null +++ b/k3s/namespaces/infra.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: infra \ No newline at end of file diff --git a/k3s/namespaces/provider.yaml b/k3s/namespaces/provider.yaml new file mode 100644 index 0000000..df39dfe --- /dev/null +++ b/k3s/namespaces/provider.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: provider \ No newline at end of file diff --git a/k3s/namespaces/trust-anchor.yaml b/k3s/namespaces/trust-anchor.yaml new file mode 100644 index 0000000..53a4084 --- /dev/null +++ b/k3s/namespaces/trust-anchor.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: trust-anchor \ No newline at end of file diff --git a/k3s/namespaces/wallet.yaml b/k3s/namespaces/wallet.yaml new file mode 100644 index 0000000..bcb65a3 --- /dev/null +++ b/k3s/namespaces/wallet.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: wallet \ No newline at end of file diff --git a/k3s/provider.yaml b/k3s/provider.yaml new file mode 100644 index 0000000..0bab8c9 --- /dev/null +++ b/k3s/provider.yaml @@ -0,0 +1,218 @@ +keycloak: + enabled: false + +apisix: + image: + debug: true + dataPlane: + ingress: + enabled: true + hostname: mp-data-service.127.0.0.1.nip.io + catchAllRoute: + enabled: false + routes: |- + - uri: /.well-known/openid-configuration + upstream: + nodes: + verifier:3000: 1 + type: roundrobin + plugins: + proxy-rewrite: + uri: /services/data-service/.well-known/openid-configuration + - uri: /.well-known/data-space-configuration + upstream: + nodes: + dsconfig:3002: 1 + type: roundrobin + plugins: + proxy-rewrite: + uri: /.well-known/data-space-configuration/data-space-configuration.json + response-rewrite: + headers: + set: + content-type: application/json + - uri: /* + upstream: + nodes: + data-service-scorpio:9090: 1 + type: roundrobin + plugins: + openid-connect: + bearer_only: true + use_jwks: true + client_id: data-service + client_secret: unused + ssl_verify: false + discovery: http://verifier:3000/services/data-service/.well-known/openid-configuration + opa: + host: "http://localhost:8181" + policy: policy/main + +vcverifier: + ingress: + enabled: true + hosts: + - host: provider-verifier.127.0.0.1.nip.io + paths: + - "/" + deployment: + logging: + level: DEBUG + verifier: + tirAddress: http://tir.127.0.0.1.nip.io:8080/ + did: ${DID} + server: + host: http://provider-verifier.127.0.0.1.nip.io:8080 + configRepo: + configEndpoint: http://credentials-config-service:8080 + alternativeConfig: /alternative-conf/server.yaml + additionalVolumes: + - name: did-material + emptyDir: {} + - name: alternative-conf + emptyDir: {} + additionalVolumeMounts: + - name: alternative-conf + mountPath: /alternative-conf + initContainers: + - name: get-did + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + apt-get -y update; apt-get -y install wget; apt-get -y install gettext-base + cd /did-material + wget http://did-helper:3002/did-material/did.env + export $(cat /did-material/did.env) + cp /original-conf/server.yaml /alternative-conf/server.yaml + envsubst < /alternative-conf/server.yaml + volumeMounts: + - name: did-material + mountPath: /did-material + - name: config-volume + mountPath: /original-conf + - name: alternative-conf + mountPath: /alternative-conf + + - name: register-at-tir + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + source /did-material/did.env + apt-get -y update; apt-get -y install curl + curl -X 'POST' 'http://tir.trust-anchor.svc.cluster.local:8080/issuer' -H 'Content-Type: application/json' -d "{\"did\": \"${DID}\", \"credentials\": []}" + volumeMounts: + - name: did-material + mountPath: /did-material + +mysql: + primary: + persistence: + enabled: false + secondary: + persistence: + enabled: false + +postgis: + primary: + persistence: + enabled: false + readReplicas: + persistence: + enabled: false + +postgresql: + primary: + persistence: + enabled: false + readReplicas: + persistence: + enabled: false + +did: + enabled: true + secret: issuance-secret + serviceType: ClusterIP + port: 3002 + cert: + country: DE + state: SAXONY + locality: Dresden + organization: M&P Operations Inc. + commonName: www.mp-operation.org + ingress: + enabled: true + host: did-provider.127.0.0.1.nip.io + +dataSpaceConfig: + enabled: true + serviceType: ClusterIP + port: 3002 + supportedModels: + - "https://raw.githubusercontent.com/smart-data-models/dataModel.Consumption/master/ConsumptionPoint/schema.json" + - "https://raw.githubusercontent.com/smart-data-models/dataModel.Consumption/master/ConsumptionCost/schema.json" + supportedProtocols: + - http + - https + authenticationProtocols: + - oid4vp + +scorpio: + ingress: + enabled: true + # only to make it available for the test initialization + hosts: + - host: scorpio-provider.127.0.0.1.nip.io + paths: + - "/" + ccs: + defaultOidcScope: + credentialType: UserCredential + trustedParticipantsLists: http://tir.trust-anchor.svc.cluster.local:8080 + +odrl-pap: + deployment: + initContainers: + - name: get-did + image: ubuntu + command: + - /bin/bash + args: + - -ec + - | + #!/bin/bash + apt-get -y update; apt-get -y install wget + cd /did-material + wget http://did-helper:3002/did-material/did.env + volumeMounts: + - name: did-material + mountPath: /did-material + additionalVolumes: + - name: did-material + emptyDir: {} + additionalVolumeMounts: + - name: did-material + mountPath: /did-material + command: + - /bin/sh + args: + - -ec + - | + #!/bin/sh + source /did-material/did.env + export GENERAL_ORGANIZATION_DID=$DID + ./application -Dquarkus.http.host=0.0.0.0 + + ingress: + enabled: true + hosts: + - host: pap-provider.127.0.0.1.nip.io + paths: + - "/" diff --git a/k3s/trust-anchor.yaml b/k3s/trust-anchor.yaml new file mode 100644 index 0000000..c72dbba --- /dev/null +++ b/k3s/trust-anchor.yaml @@ -0,0 +1,19 @@ +trusted-issuers-list: + # -- it should work as the TrustedIssuersRegistry of the dataspace, thus we publish this api only + ingress: + tir: + enabled: true + hosts: + - host: tir.127.0.0.1.nip.io + til: + enabled: true + hosts: + - host: til.127.0.0.1.nip.io + +mysql: + primary: + persistence: + enabled: false + secondary: + persistence: + enabled: false \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3432f8a --- /dev/null +++ b/pom.xml @@ -0,0 +1,324 @@ + + + 4.0.0 + + org.fiware.dataspace + connector + 0.0.1 + pom + + it + + + + 17 + 17 + + UTF-8 + 1.2.4 + 6.13.0 + 3.1.1 + 2.4 + 3.3.0 + 3.1.1 + 3.0.0 + 3.0.0 + + ${project.basedir} + + + + + + src/main/resources + true + + + + + src/test/resources + true + + + + + org.apache.maven.plugins + maven-install-plugin + ${version.org.apache.maven.plugins.maven-install} + + + org.apache.maven.plugins + maven-source-plugin + ${version.org.apache.maven.plugins.maven-source} + + + + org.apache.maven.plugins + maven-dependency-plugin + ${version.org.apache.maven.plugins.maven-dependency} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${version.org.apache.maven.plugins.maven-jar} + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.org.apache.maven.plugins.maven-surefire} + + + default-test + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${version.org.apache.maven.plugins.maven-failsafe} + + false + + **/RunCucumberTest.java + + + + + test + integration-test + + integration-test + + + + verify + integration-test + + verify + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + maven-resources-plugin + + true + + + + copy-resources-trust-anchor + prepare-package + + copy-resources + + + ${project.build.directory}/charts/trust-anchor + + + ${main.basedir}/charts/trust-anchor + + + + + + copy-resources-dsc + prepare-package + + copy-resources + + + ${project.build.directory}/charts + + + ${main.basedir}/charts + + + + + + copy-resources-namespaces + prepare-package + + copy-resources + + + ${project.build.directory}/k3s/namespaces + + + ${main.basedir}/k3s/namespaces + + + + + + copy-resources-infra + prepare-package + + copy-resources + + + ${project.build.directory}/k3s/infra + + + ${main.basedir}/k3s/infra + + + + + + + + + io.kokuwa.maven + helm-maven-plugin + ${version.io.kokuwa.helm-maven-plugin} + + true + + + + template-dsc-provider + + init + dependency-update + template + + package + + ${project.build.directory}/charts/data-space-connector + false + true + ${project.build.directory}/k3s/dsc-provider + --name-template=provider --namespace=provider -f ${main.basedir}/k3s/provider.yaml + + + + + template-dsc-consumer + + init + dependency-update + template + + package + + ${project.build.directory}/charts/data-space-connector + false + true + ${project.build.directory}/k3s/dsc-consumer + --name-template=consumer --namespace=consumer -f ${main.basedir}/k3s/consumer.yaml + + + + + template-trust-anchor + + init + dependency-update + template + + package + + ${project.build.directory}/charts/trust-anchor + false + true + ${project.build.directory}/k3s/trust-anchor + --name-template=trust-anchor --namespace=trust-anchor -f ${main.basedir}/k3s/trust-anchor.yaml + + + + + + + io.kokuwa.maven + k3s-maven-plugin + ${version.io.kokuwa.maven.k3s-plugin} + + true + + 8080:8080 + + + + + create-namespaces + deploy + + run + apply + + + false + ${project.build.directory}/k3s/namespaces + + + + apply-participants + deploy + + apply + + + ${project.build.directory}/k3s + 500 + + + + + + + + + local + + + + maven-resources-plugin + + false + + + + io.kokuwa.maven + helm-maven-plugin + + false + + + + io.kokuwa.maven + k3s-maven-plugin + + false + + + + org.apache.maven.plugins + maven-failsafe-plugin + + true + + + + + + + + + \ No newline at end of file