From 21e232894dc36dbd3dc553351bbde456ae3cdb01 Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 11 May 2023 20:26:10 +0200 Subject: [PATCH] Fix formatting. Use AWS SDK compatible with Quarkus native images (#47). Build docker image on every build as latest (#47). Remove UUID from vault JWE (#4/#6). Add storage class, bucket acceleration and bucket encryption options to storage profiles (#44). Improved error handling/logging and openapi response documentation (#6). Improved error handling/logging and openapi response documentation (#6). Improved error handling/logging for storage profiles and bucket creation (#6/#3/17). Decouple the client protocol identifier (s3-hub/s3-hub-sts) and the discriminator values (S3/S3STS) in the backend DB tables through openapi-generated client code (#6). Fix linting (#6). Hierarchical DB schema for storage profiles based on DiscriminatorColumn (#6). Use discriminatorProperty openapi scheme annotations for use with openapi-generator's oneOf discriminator lookup (#6). Use optional chaining to make linter happy again (TS18048) (#17/#3). Formatting. Improved error messages (#3/#17). Slim storage profile for what can be fetched from /api/config (#6). Fix openapi/markdown documentation for openapi-generator (#6). Do not show region/GetBucketLocation in permanent case. Update openapi/markdown documentation for storage configurations (#6/#17). Update openapi/markdown documentation for storage configurations (#6) Pull up automatic access grant top-level in vault JWE along key and backend (#13). Create bucket as first call in vault creation (#3). Bufgix GET for individual storage profile allow all users (not only admins) (#17). Show aws cli command for setting CORS (#17). Improve validation message vault template upload frontend permanent (#17) Implement GET for individual storage profile and DELETE WiP (#17) Bugfix vault template upload frontend permanent (#17) Bugfix vault template upload frontend permanent (#17). Fix linting (#17). Validate permanent credentials before uploading vault template (#17). Bugfix vault template upload frontend permanent (#17) Fix missing aud claim required for MinIO. Update documentation uploading storage profiles with admin role (#6). Fix base uri to open in Cipherduck desktop. Enforce authentication for storage profile api. Fix enable S3 Versioning upon bucket creation (#44). Formatting. Enable S3 Versioning upon bucket creation (#44). Enable S3 Versioning upon bucket creation (#44). Implement migration path automatic access grant with WoT (#13 / #43). Document decision remove access to vault. Rename KeycloakCryptomatorVaultsHelper. Rename S3StorageHelper. Fix linting. Fix upload vault template to bucket heading. Use *.cyberduckprofile for hub, s3-hub, s3-hub-sts. Fix cipherduck start/end extension markers. Refactoring (R3) storage profile service persistence (#4 #6). Refactoring (R3) storage profile service WiP (#4 #6). Refactoring (R3) storage profile service WiP (#4 #6). Set container image group and name in application.properties instead of pom.xml (#47). Update setup documentation(#47). Set container image group and name (#47) Re-enable docker image build and pushing to registry. Remove cipherduckhubbookmark endpoint, add hub UUID to config endpoint to allow client-side hub-specific profile and bookmark generation (#6). Use better name VaultRequested instead of VaultR for Tag Key in assuming second role in chain for AWS (review dko). Get full region list from AWS SDK instead of hard-coding (code review overheadhunter). Extract global constant axiosUnAuth in backend.ts (code review overheadhunter). Inline hubbookmark.duck in order to avoit poentital special handling when using GraalVM to build a native image. Apply suggestions from code review More idiomatic usage of Java stream API. Co-authored-by: Sebastian Stenzel Update backend/src/main/java/org/cryptomator/hub/api/cipherduck/BackendsConfigResource.java Co-authored-by: Sebastian Stenzel Remove obsolete added into line in diff to upstream. Comply with vue-tsc (Vue 3 Type-Checking). Update README.md Co-authored-by: Sebastian Stenzel Moving S3 policies away from src/main/resources. Remove CreateVaultS3.vue in order to rebase changes in CreateVault.vue from upstream. Bugfix description displayed as false when vaults created in hub introduced through forking CreateVaultS3.vue from CreateVault.vue and then missing breaking API change. By default, in dev-realm.json, map only realm roles into access token in cryptomator and cryptomator hub clients, but not client roles. Separate roles in MinIO: bucket creation (cryptomator and cryptomatorhub cliients) and bucket access (for cryptomatorvaults client) (#10 #41) Implement template upload for permanent shared credentials (#17). Tentative implementation clean-up sync deleting dangling roles in cryptomatorvaults and corresonding client scopes (#41). Extract profiles and simplify vault jwe (#28, #6). Variable cleanup in CreateVaultS3.vue Comply with pre-release API change in granting access to newly created vault (https://github.com/cryptomator/hub/commit/1c2133d9868a2de3922aaa73b7b1cf3d90063cca). Post-rebase fix: remove manage-realm from syncer role in dev-realm.json (#41). Move staging/testing properties into custom application.properties (#41). Distinguish stsRoleArn for client and hub when creating bucket, update documentation (#12 #23). Bugfix download template in CreateVaultS3.vue User cipherduck profiles to simplify hub application.properties, add AWS permanent credentials to backend configurations application.properties (#28). Get AWS-STS back to work again, update documentation (#10 #23). Add developer flag for showing vaultId in VaultDetails and VaultList. Add missing import ArrowDownTrayIcon in CreateVaultS3.vue. BackendsConfigDto instead of Any in backend.ts Remove unnecessary manage-realm role for syncer (#41). Automatic Access Grant Flag upon vault creation (#13). Extract hard-coded cryptomatorvaults client to application.properties (#41). Get hubId from backends config service (#10 #41). Implement sharing vaults with groups and unsharing with users/groups; token-exchange into separate client (#10 #41). Cleanup application.properties Cleanup application.properties Remove proxyman stuff again as not used. Complete region list. Remove obsolete dependencies in pom.xml. Refactoring protocol serialization (#4). Remove obsolete CipherduckBookmark.vue (#16). Remove obsolete CipherduckBookmark.vue (#16). Localization DE (#31). Mark cipherduck extensions in vues. Shared long-living credentials: ask for bucket name and offer vault template download after vault creation (#17). Shared long-living credentials (#17) Use inline policy to restrict credentials passed to Hub backend (#3). Allow for choosing region upon vault creation (#3). Cleanup and documentation VaultJWEBackend (#23 #6). Button "Open in Cipherduck" not necessary in vault details, as it is confusing (does not open single vault) and on top of the vault list is still visible (#16). Cleanup and documentation VaultJWEBackend (#23 #6). Cleanup and documentation VaultJWEBackend (#23 #6). Cleanup and documentation VaultJWEBackend (#15 #23 #6). Bugfix backend/storage configuration not re-encoded upon granting access (#13). Cleanup bucket prefix and documentation (#15 #23 #6). Implement token-exchange to get scoped token for AWS with testing.hub.cryptomator.org (#41 #10 #23 #3). Gitignore local backend/config/application.properties. Updated top-level README.md for Cipherduck. Show Vault ID in VaultDetails for debugging. Implement token-exchange to get scoped token for MinIO (#41 #10 #23 #3). AssumeRoleWithWebIdentity (MinIO + AWS) in frontend and pass temporary credentials to backend: get rid of policy upload and use only AWS client, admin documentation (#3, #23, #10). AssumeRoleWithWebIdentity (MinIO + AWS) in frontend and pass temporary credentials to backend: get rid of policy upload and use only AWS client, admin documentation (#3, #23, #10). Add hub frontend vault storage configuration for STS (MinIO + AWS) (#3). Cipherduckhubbookmark end point for 1 vault = 1 storage (#4). Use StorageConfig service in frontend to get values (#3). Add configuration for hub frontend vault storage configuration for STS (MinIO + AWS) (#3). Add admin Documentation for setting up OIDC Provider at AWS/MinIO and testing vault creation (#23). Add hub frontend vault storage configuration for STS (MinIO + AWS) (#3). Add protocol field to StorageDto (#6). Refactor StorageDto into record instead of POJO. Update frontend/src/common/backend.ts Co-authored-by: Sebastian Stenzel Update backend/src/main/java/org/cryptomator/hub/api/StorageResource.java Co-authored-by: Sebastian Stenzel Fix failing tests as Keycloak is not available at quarkus test time. Comment out sonarcloud in github action. Update issue templates (#33) added "open bookmark" button in vault details (just in case), hid "download vault template" button Use 'x-cipherduck-action' instead of 'io.mountainduck' for OAuth custom scheme handling (#28). added "open bookmark" button in vault list Add Hub Id as UUID in hub bookmark to prevent adding the same bookmark multiple times (#29). renamed most obvious instances of Cryptomator Hub to Cipherduck Bugfix missing description in openapi.json for vault storage shared long-living credentials API (#17). cleaned up frontend Implement cipherduck hub bookmark download from browser (#16). Implement cipherduck hub bookmark download frontend page (#16). Bugfix missing constructor for first version hub frontend vault storage shared long-living credentials (#17). Implement first version hub frontend vault storage shared long-living credentials (#17). Implement cipherduck hub bookmark endpoint (#16). Use amr claim instead of aud claim for now (#10). Use cryptomator client id in staging keycloak as well (#10). Use vault instead of vault user attribute (#10). Remove admin role for syncer (#10). Remove minio client id. Switch /api/config/cipherduckprofile to local MinIO configuration to fix HubIntegration test in client project. Update TODOs. Bugfix empty attributes in keycloak. Config cipherduck-staging (one role for all buckets). Set directAccessGrantsEnabled to false. Simplify concat Add top-level .gitignore (ignoring top-level .idea folder). Add /api/config/cipherduckprofile v0. Remove obsolete dependencies to commons-io and qute. Move GeneratePolicy back to duck again. Dev-realm with minio client_id. Upload bucket policy (aws cli call in backend for now) upon vault creation and add vaultId to keycloak upon vault JWE upload. TODO: create bucket upon vault creation. Update application.properties: comment out proxyman.local Improve local dev setup description in README. Add user-001 to dev-realm.json. Add configuration with alternative host proxyman.local instead of localhost name as requests to localhost are bypassing configured proxies. --- .github/ISSUE_TEMPLATE/story.md | 23 + .github/workflows/build.yml | 74 +- .gitignore | 1 + README.md | 16 +- backend/.gitignore | 2 +- backend/CIPHERDUCK.md | 296 +++ backend/pom.xml | 19 +- .../setup/aws_static/aws_static_profile.json | 6 + backend/setup/aws_sts/aws_sts_profile.json | 16 + .../cipherduck_chain_01_permissionpolicy.json | 13 + .../cipherduck_chain_01_trustpolicy.json | 19 + .../cipherduck_chain_02_permissionpolicy.json | 26 + .../cipherduck_chain_02_trustpolicy.json | 20 + .../aws_sts/createbucketpermissionpolicy.json | 29 + .../aws_sts/createbuckettrustpolicy.json | 16 + .../minio_static/minio_static_profile.json | 9 + .../setup/minio_sts/accessbucketpolicy.json | 30 + .../setup/minio_sts/createbucketpolicy.json | 27 + .../setup/minio_sts/minio_sts_profile.json | 26 + .../org/cryptomator/hub/api/AuthorityDto.java | 17 +- .../cryptomator/hub/api/ConfigResource.java | 22 +- .../cryptomator/hub/api/VaultResource.java | 32 + .../api/cipherduck/AutomaticAccessGrant.java | 13 + .../hub/api/cipherduck/CipherduckConfig.java | 86 + .../hub/api/cipherduck/StorageDto.java | 28 + .../hub/api/cipherduck/StorageProfileDto.java | 91 + .../cipherduck/StorageProfileResource.java | 144 ++ .../api/cipherduck/StorageProfileS3Dto.java | 91 + .../cipherduck/StorageProfileS3STSDto.java | 190 ++ .../hub/api/cipherduck/StorageResource.java | 85 + .../api/cipherduck/VaultJWEBackendDto.java | 30 + .../api/cipherduck/VaultJWEPayloadDto.java | 17 + .../cipherduck/storage/S3StorageHelper.java | 205 ++ .../KeycloakCryptomatorVaultsHelper.java | 235 ++ .../KeycloakCryptomatorVaultsSyncer.java | 24 + .../entities/cipherduck/StorageProfile.java | 33 + .../entities/cipherduck/StorageProfileS3.java | 35 + .../cipherduck/StorageProfileS3STS.java | 61 + .../src/main/resources/application.properties | 33 +- backend/src/main/resources/dev-realm.json | 339 ++- .../hub/flyway/V80__StorageProfile.sql | 56 + frontend/index.html | 2 +- frontend/package-lock.json | 2157 +++++++++++++++-- frontend/package.json | 2 + frontend/src/common/backend.ts | 92 + frontend/src/common/crypto.ts | 49 +- frontend/src/common/settings.ts | 2 + frontend/src/common/vaultconfig.ts | 4 +- frontend/src/components/CreateVault.vue | 588 ++++- frontend/src/components/NavigationBar.vue | 4 +- frontend/src/components/UnlockError.vue | 4 +- frontend/src/components/VaultDetails.vue | 22 +- frontend/src/components/VaultList.vue | 23 +- frontend/src/css/fonts.css | 6 +- .../src/fonts/quicksand-bold.reduced.woff2 | Bin 2536 -> 1996 bytes frontend/src/i18n/de-DE.json | 16 +- frontend/src/i18n/en-US.json | 15 +- .../common/resources/css/fonts.css | 6 +- .../fonts/quicksand-bold.reduced.woff2 | Bin 2536 -> 1996 bytes 59 files changed, 5228 insertions(+), 299 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/story.md create mode 100644 .gitignore create mode 100644 backend/CIPHERDUCK.md create mode 100644 backend/setup/aws_static/aws_static_profile.json create mode 100644 backend/setup/aws_sts/aws_sts_profile.json create mode 100644 backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json create mode 100644 backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json create mode 100644 backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json create mode 100644 backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json create mode 100644 backend/setup/aws_sts/createbucketpermissionpolicy.json create mode 100644 backend/setup/aws_sts/createbuckettrustpolicy.json create mode 100644 backend/setup/minio_static/minio_static_profile.json create mode 100644 backend/setup/minio_sts/accessbucketpolicy.json create mode 100644 backend/setup/minio_sts/createbucketpolicy.json create mode 100644 backend/setup/minio_sts/minio_sts_profile.json create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/AutomaticAccessGrant.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/CipherduckConfig.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageDto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileDto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileResource.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java create mode 100644 backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java create mode 100644 backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java create mode 100644 backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java create mode 100644 backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java create mode 100644 backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql create mode 100644 frontend/src/common/settings.ts diff --git a/.github/ISSUE_TEMPLATE/story.md b/.github/ISSUE_TEMPLATE/story.md new file mode 100644 index 000000000..db91f8c49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/story.md @@ -0,0 +1,23 @@ +--- +name: Story +about: Persona needs for purpose. +title: "[Story]" +labels: '' +assignees: '' + +--- + +### Story +* **Persona**: +* **Need**: +* **Purpose**: + + +### Acceptance Criteria +- [ ] + +### Open Questions + +### Context + +### Implementation diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5ead6502..1cb91da48 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,58 +41,72 @@ jobs: - name: Deploy frontend working-directory: frontend run: npm run dist - - name: SonarCloud Scan Frontend - uses: SonarSource/sonarcloud-github-action@master - with: - projectBaseDir: frontend - args: > - -Dsonar.organization=cryptomator - -Dsonar.projectKey=cryptomator_hub_frontend - -Dsonar.typescript.tsconfigPath=tsconfig.json - -Dsonar.sources=src/ - -Dsonar.tests=test/ - -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# / cipherduck start commented out +# - name: SonarCloud Scan Frontend +# uses: SonarSource/sonarcloud-github-action@master +# with: +# projectBaseDir: frontend +# args: > +# -Dsonar.organization=cryptomator +# -Dsonar.projectKey=cryptomator_hub_frontend +# -Dsonar.typescript.tsconfigPath=tsconfig.json +# -Dsonar.sources=src/ +# -Dsonar.tests=test/ +# -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any +# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# \ cipherduck end commented out - uses: actions/setup-java@v2 with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} cache: 'maven' - - name: Cache SonarCloud packages - uses: actions/cache@v2 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar +# / cipherduck start commented out +# - name: Cache SonarCloud packages +# uses: actions/cache@v2 +# with: +# path: ~/.sonar/cache +# key: ${{ runner.os }}-sonar +# restore-keys: ${{ runner.os }}-sonar +# \ cipherduck end commented out - name: Build and test backend working-directory: backend run: > mvn -B clean verify - org.sonarsource.scanner.maven:sonar-maven-plugin:sonar - -Dsonar.projectKey=cryptomator_hub_backend - -Dsonar.organization=cryptomator - -Dsonar.host.url=https://sonarcloud.io - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# / cipherduck start commented out +# org.sonarsource.scanner.maven:sonar-maven-plugin:sonar +# -Dsonar.projectKey=cryptomator_hub_backend +# -Dsonar.organization=cryptomator +# -Dsonar.host.url=https://sonarcloud.io +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +# \ cipherduck end commented out - id: get_tag - if: inputs.tag != '' || github.ref_type == 'tag' || contains(github.event.head_commit.message, '[build image]') + # / cipherduck start commented out + # if: inputs.tag != '' || github.ref_type == 'tag' || contains(github.event.head_commit.message, '[build image]') + # \ cipherduck end commented out run: | if [[ ! -z "${{ inputs.tag }}" ]]; then TAG="${{ inputs.tag }}" elif [[ ${{ github.ref_type }} == 'tag' || ${{ github.ref_name }} == 'develop' ]]; then TAG="${{ github.ref_name }}" else - TAG="commit-${{ github.sha }}" + # / cipherduck start modification + #TAG="commit-${{ github.sha }}" + # use latest by default as our container registry has limited capacity + TAG="latest" + # \ cipherduck end modification fi echo tag=${TAG} >> "$GITHUB_OUTPUT" - name: Ensure to use tagged version if: startsWith(github.ref, 'refs/tags/') run: mvn versions:set --file ./backend/pom.xml -DnewVersion=${GITHUB_REF##*/} - name: Build and push container image - if: github.event.inputs.tag != '' || startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[build image]') + # / cipherduck start commented out + #if: github.event.inputs.tag != '' || startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[build image]') + # \ cipherduck end commented out working-directory: backend run: mvn -B clean package -DskipTests env: diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..62c893550 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 58efdc6dd..d0fa138af 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ [![CI Build](https://github.com/cryptomator/hub/actions/workflows/build.yml/badge.svg)](https://github.com/cryptomator/hub/actions/workflows/build.yml) -# Cryptomator Hub +# Cipherduck Hub: the secure and easy way to work in teams -Hub consists of these components: +Cipherduck Hub bring zero-config storage management and zero-knowledge key management for teams and organizations. + +It easily integrates into your existing identity management incl. OpenID Connect, SAML, and LDAP. +As usual, your favorite cloud service remains your free choice [^1]. + +[^1]: Currently, we support AWS S3 and MinIO S3. + +Cipherduck consists of Cipherduck Hub and Cipherduck Client. Cipherduck Client is based on [Mountain Duck](https://mountainduck.io/). +Cipherduck Hub is based on [Cryptomator Hub](https://github.com/cryptomator/hub/), consisting of these components: ## Web Frontend @@ -14,4 +22,6 @@ During development, run Quarkus from the `backend` dir as explained in [its READ ## Custom Keycloak Image -We add a custom theme to the base keycloak image, as explained in [its README file](keycloak/README.md).: \ No newline at end of file +We add a custom theme to the base keycloak image, as explained in [its README file](keycloak/README.md).: + + diff --git a/backend/.gitignore b/backend/.gitignore index 35e9ee6fe..da1e16343 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -45,4 +45,4 @@ nb-configuration.xml *.rej # Local environment -.env +.env \ No newline at end of file diff --git a/backend/CIPHERDUCK.md b/backend/CIPHERDUCK.md new file mode 100644 index 000000000..6cf043161 --- /dev/null +++ b/backend/CIPHERDUCK.md @@ -0,0 +1,296 @@ +Cipherduck hub setup admin documentation +======================================== + + +MinIO +----- + +### Setup MinIO + +Documentation + +* [MinIO OpenID Connect Access Management](https://min.io/docs/minio/linux/administration/identity-access-management/oidc-access-management.html) +* [MinIO Client Reference `mc idp openid`](https://min.io/docs/minio/linux/reference/minio-mc/mc-idp-openid.html) +* [MinIO Security Token Service `AssumeRoleWithWebIdentity](https://min.io/docs/minio/linux/developers/security-token-service/AssumeRoleWithWebIdentity.html) + +``` +minio server data --console-address :9001 +``` + +Or containerized: + +``` +export MINIO_ROOT_USER= +export MINIO_ROOT_PASSWORD= +export MINIO_API_CORS_ALLOW_ORIGIN=testing.hub.cryptomator.org +docker run -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=$MINIO_ROOT_USER -e MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD -e MINIO_API_CORS_ALLOW_ORIGIN=$MINIO_API_CORS_ALLOW_ORIGIN quay.io/minio/minio server /data --console-address ":9001" +``` + +Side-note: MinIO does not support bucket CORS API, +see [MinIO - Unsupported S3 Bucket APIs](https://min.io/docs/minio/linux/operations/concepts/thresholds.html#unsupported-s3-bucket-apis) + +#### Policy and OIDC provider for MinIO + +Add role for creating buckets with prefix `cipherduck` and uploading `vault.cryptomator`, as well as RW to access to +buckets through `client_id` claim in JWT token. Adapt bucket prefix in + +* [setup/minio_sts/createbucketpolicy.json](setup%2Fminio_sts%2Fcreatebucketpolicy.json) + +Side-note: MinIO does not allow for multiple OIDC providers with the same client ID: + +> mc: Unable to add OpenID IDP config to server. Client ID XYZ is present with multiple OpenID configurations. + +This is not a problem as we leave the claim specifying the vault unset or pointing to a non-existing vault. + +```shell +mc alias set myminio http://127.0.0.1:9000 minioadmin minioadmin +mc admin policy create myminio cipherduckcreatebucket setup/minio_sts/createbucketpolicy.json +mc admin policy create myminio cipherduckaccessbucket setup/minio_sts/accessbucketpolicy.json +``` + +Add a new OIDC provider, vault creation and vault access policy in MinIO: + +```shell +WELL_KNOWN=https://testing.hub.cryptomator.org/kc/realms/cipherduck/.well-known/openid-configuration +#WELL_KNOWN=http://localhost:8180/realms/cryptomator/.well-known/openid-configuration +mc idp openid add myminio cryptomator \ + config_url="$WELL_KNOWN" \ + client_id="cryptomator" \ + client_secret="ignore-me" \ + role_policy="cipherduckcreatebucket" +mc idp openid add myminio cryptomatorhub \ + config_url="$WELL_KNOWN" \ + client_id="cryptomatorhub" \ + client_secret="ignore-me" \ + role_policy="cipherduckcreatebucket" +mc idp openid add myminio cryptomatorvaults \ + config_url="$WELL_KNOWN" \ + client_id="cryptomatorvaults" \ + client_secret="ignore-me" \ + role_policy="cipherduckaccessbucket" +mc admin service restart myminio +``` + +Extract the policy ARN: + +```shell +mc idp openid ls myminio +╭──────────────────────────────────────────────────────────────────────────╮ +│ On? Name RoleARN │ +│ 🔴 (default) │ +│ 🟢 cryptomator arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE │ +│ 🟢 cryptomatorhub arn:minio:iam:::role/HGKdlY4eFFsXVvJmwlMYMhmbnDE │ +│ 🟢 cryptomatorvaults arn:minio:iam:::role/Hdms6XDZ6oOpuWYI3gu4gmgHN94 │ +╰──────────────────────────────────────────────────────────────────────────╯ + + + mc idp openid info myminio cryptomator +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ client_id: cryptomator │ +│client_secret: ignore-me │ +│ config_url: https://testing.hub.cryptomator.org/kc/realms/cipherduck/.well-known/openid-configuration │ +│ enable: on │ +│ roleARN: arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE │ +│ role_policy: cipherduckcreatebucket │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +``` + +### Hub configuration + +See [application.properties](config%2Fapplication.properties) + +AWS +--- + +### Setup AWS + +#### Setup AWS: OIDC provider + +Documentation: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html + +```shell +openssl s_client -servername testing.hub.cryptomator.org -showcerts -connect testing.hub.cryptomator.org:443 > testing.hub.cryptomator.org.crt + +vi testing.hub.cryptomator.org.crt ... +(remove the irrelevant parts from the chain) + +cat testing.hub.cryptomator.org.crt +-----BEGIN CERTIFICATE----- +MIIGBDCCBOygAwIBAgISA1CGKN3OkGJihg/qGhz2fl3fMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yMzExMTIxMzAyMTdaFw0yNDAyMTAxMzAyMTZaMCYxJDAiBgNVBAMT +G3Rlc3RpbmcuaHViLmNyeXB0b21hdG9yLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBALWWmJr7lckOPCysl8p8FywJ2BwfCfdqMqTeb7KdOa3Zd9kb +rb0dYUAs6cs4XKIxSBzKTDJAZiE5d2/iXUgHIBS8hDjG8U40EFaKDTc/JugOSovs +HB6FQTi4YCMNfm3oMBiREMXYQTEKErBFfECbtGw8mTua2suT6Uc7lwj91qbPO6BN +TROk0Az1NcifYOz8lMZhelg0WXEa10YfalaKGtjh4srMBv0rT85PpXaJXaNp58Ls +4Psf/YlPjGJOhevnyAuqZouUD9sz7gZX8WvQ87y9uTXpDoarySh/0nppYLPZTDty +sI3LeVwwrf4ir5jObVgjkH1CdS8kj/ueKLLW0BBqSX/9oji9o1zFJlBeRcWbeW08 +SD3+7292cy+zpNo3Y7xEFxGs0SVlJjTRk4cf6edkVq5QzTPqIF9FSn6tgXC6OTJi +ISHnLGvkuSOzCieADPwjlYJiix3duK+0rpeN3xH3/NnyvPnncbWr/KLwwGE/tsHx +orv1XLXkV0nmD9MDvE1gqRd7m7n3PwXEojz2Ih37i4bowFx2jYy6acAyY0KJSWwE +3Rl2BRvOqXY1AOZC2MKOp7mb3hbryr8pzUPb0j4p3iOmOG9MgUQydKLyE97W1Ucd +PRQMHdoG+EKnDeaauKdZ/3Lj0jMJ1CKlmYOB5qShHv1XCR5uimouioQkoJTFAgMB +AAGjggIeMIICGjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHkBSFhuApvRJvGqRHZg +5t183UMCMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUF +BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsG +AQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCYGA1UdEQQfMB2CG3Rlc3Rp +bmcuaHViLmNyeXB0b21hdG9yLm9yZzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQUG +CisGAQQB1nkCBAIEgfYEgfMA8QB2ADtTd3U+LbmAToswWwb+QDtn2E/D9Me9AA0t +cm/h+tQXAAABi8PXIB0AAAQDAEcwRQIhAPOlsQr63JOSMbTFWOM746oA7i4HQ+hl +p7M3pRpG4HYQAiBKqLSDsx1FdI18Fax3k7zkCgsY8x96ZAQvVUfdch0xoAB3AO7N +0GTV2xrOxVy3nbTNE6Iyh0Z8vOzew1FIWUZxH7WbAAABi8PXIBwAAAQDAEgwRgIh +AOZskIE18A5sTthKz6w3wMvIocbaoj3UCTCIAXWVJJNzAiEAmMWS709vLq/WOPG0 +5hb6lBPn6NRnjizJaNEnj/ts71EwDQYJKoZIhvcNAQELBQADggEBADiSgsGpOKqZ +0kzeIS9x7vJlc3I0lnScB9JjxJyLoZFs//T4SNWE18zFxnzVspWRnwu4NTmuGURv +6RWJ8RAznYwjZCnVDdQREUSX7wahzGdz+3GalRaIYngkvwHOhT+aGLbrKRjz+Pfh +13qMStwjlfA6iSofHqVeQFCf48itgeVjNbpdZKEOLwdiV+JMwpT4n/i0nfVwWkaG +RcEWn8S4gfSq1iZ/LAhWdyB0QJ4EcCO6mx02wABxbQibPc5FM8Q64j37TizHniVu +hs+X7qFNDF/jvbob3sL09e0BLjiZWxVasAHiAAaZONTRV0N5YYV56F5br/vnegic +u3AvSS5HW70= +-----END CERTIFICATE----- + + +openssl x509 -in testing.hub.cryptomator.org.crt -fingerprint -sha1 -noout | sed -e 's/://g' | sed -e 's/[Ss][Hh][Aa]1 [Ff]ingerprint=//' +BE21B29075BF9F3265353F8B85208A8981DAEC2A + +aws iam create-open-id-connect-provider --url https://testing.hub.cryptomator.org/kc/realms/cipherduck --client-id-list cryptomator cryptomatorhub --thumbprint-list BE21B29075BF9F3265353F8B85208A8981DAEC2A +{ + "OpenIDConnectProviderArn": "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck1" +} + +aws iam list-open-id-connect-providers + +aws iam get-open-id-connect-provider --open-id-connect-provider-arn arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck +{ + "Url": "testing.hub.cryptomator.org/kc/realms/cipherduck", + "ClientIDList": [ + "cryptomatorhub", + "cryptomator" + ], + "ThumbprintList": [ + "a053375bfe84e8b748782c7cee15827a6af5a405" + ], + "CreateDate": "2023-11-13T13:51:32.729000+00:00", + "Tags": [] +} +``` + +#### Setup AWS: roles + +Add role for creating buckets with prefix `cipherduck` and uploading `vault.cryptomator`, adapt OIDC provider in trust +policy and bucket prefix in permission policy: + +* [aws/createbuckettrustpolicy.json](./setup%2Faws%2Fcreatebuckettrustpolicy.json) +* [aws/createbucketpermissionpolicy.json](setup%2Faws%2Fcreatebucketpermissionpolicy.json) + +Add roles for role chaining, adapt OIDC provider in trust policy and bucket prefix in permission policy: + +* [aws/cipherduck_chain_01_trustpolicy.json](setup%2Faws%2Fcipherduck_chain_01_trustpolicy.json) +* [aws/cipherduck_chain_01_permissionpolicy.json](setup%2Faws%2Fcipherduck_chain_01_permissionpolicy.json) +* [aws/cipherduck_chain_02_trustpolicy.json](setup%2Faws%2Fcipherduck_chain_02_trustpolicy.json) +* [aws/cipherduck_chain_02_permissionpolicy.json](setup%2Faws%2Fcipherduck_chain_02_permissionpolicy.json) + +```shell +aws iam create-role --role-name cipherduck-createbucket --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscreatebuckettrustpolicy.json +aws iam put-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket --policy-document file://src/main/resources/cipherduck/setup/aws_stscreatebucketpermissionpolicy.json + + +aws iam create-role --role-name cipherduck_chain_01 --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_01_trustpolicy.json +aws iam put-role-policy --role-name cipherduck_chain_01 --policy-name cipherduck_chain_01 --policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_01_permissionpolicy.json + +sleep 10; + +aws iam create-role --role-name cipherduck_chain_02 --assume-role-policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_02_trustpolicy.json +aws iam put-role-policy --role-name cipherduck_chain_02 --policy-name cipherduck_chain_02 --policy-document file://src/main/resources/cipherduck/setup/aws_stscipherduck_chain_02_permissionpolicy.json +``` + +Checking roles: + +```shell +aws iam get-role --role-name cipherduck-createbucket +aws iam get-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket +``` + +```shell +TOKEN=`curl -v -X POST https://testing.hub.cryptomator.org/kc/realms/cipherduck/protocol/openid-connect/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "client_id=cryptomator" \ + -d "scope=openid" \ + -d "grant_type=password" \ + -d "username=admin" \ + -d "password=$PASSWORD" | jq ".id_token" | tr -d '"'` + +jwtd $TOKEN +aws sts assume-role-with-web-identity --role-arn "arn:aws:iam::930717317329:role/cipherduck-createbucket" --role-session-name="blabla" --web-identity-token $TOKEN +``` + +### Hub configuration + +See [application.properties](config%2Fapplication.properties). The configured prefix must match the ones configured in +the AWS/MinIO setup. Take the role arns from the AWS/MinIO setup. + +### AWS cleanup + +```shell +aws iam delete-role-policy --role-name cipherduck-createbucket --policy-name cipherduck-createbucket +aws iam delete-role --role-name cipherduck-createbucket +aws iam delete-role-policy --role-name cipherduck_chain_01 --policy-name cipherduck_chain_01 +aws iam delete-role --role-name cipherduck_chain_01 +aws iam delete-role-policy --role-name cipherduck_chain_02 --policy-name cipherduck_chain_02 +aws iam delete-role --role-name cipherduck_chain_02 +``` + +Storage Profiles +---------------------------------------------------------- + +### Introduction + +| Term | Description | Usage in Cipherdu^ck | +|---------------------------------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| +| [Bookmark](https://docs.cyberduck.io/cyberduck/bookmarks/) | Refers to connection profile/profile and adds properties like. | For hubs and vaults. | +| [Protocol](https://docs.cyberduck.io/protocols/) | | `hub` and `s3-sts` | +| [Connection Profile](https://docs.cyberduck.io/protocols/profiles/) | Refers to protocol and overrides properties | Used internally. | +| Vault [JWE](https://datatracker.ietf.org/doc/html/rfc7516) | JSON Web Encryption for encrypted JSON-based data structures | Contains the vault masterkey for decrypting data plus all information required to create vault bookmarks. | + +Note that properties in `application.properties` use dashed notation instead of Camel Case in JWE and Java Dtos, +see [Quarkus Config Reference Guide](https://quarkus.io/guides/config-reference) for details. + +### API documentation + +See http://localhost:8080/q/openapi?format=json or http://localhost:8080/q/swagger-ui/ + +### Examples + +* [aws_sts_profile.json](setup%2Faws_sts%2Faws_sts_profile.json) +* [minio_sts_profile.json](setup%2Fminio_sts%2Fminio_sts_profile.json) +* [aws_static_profile.json](setup%2Faws_static%2Faws_static_profile.json) +* [minio_static_profile.json](setup%2Fminio_static%2Fminio_static_profile.json) + +### Upload storage profiles + +You need to be a hub admin user. If direct access grant is enabled: + +``` +export HUB_API_BASE=http://localhost:8080/api +export ACCESS_TOKEN=`curl -v -X POST http://localhost:8180/realms/cryptomator/protocol/openid-connect/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "client_id=cryptomator" \ + -d "grant_type=password" \ + -d "username=admin" \ + -d "password=admin" | jq ".access_token" | tr -d '"'` +curl -X PUT $HUB_API_BASE/storageprofile/s3sts -d @setup/minio_sts/minio_sts_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" +curl -X PUT $HUB_API_BASE/storageprofile/s3 -d @setup/minio_static/minio_static_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" +curl -X PUT $HUB_API_BASE/storageprofile/s3sts -d @setup/aws_sts/aws_sts_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" +curl -X PUT $HUB_API_BASE/storageprofile/s3 -d @setup/aws_static/aws_static_profile.json -v -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" +curl $HUB_API_BASE/storageprofile/ -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +Else, use [hub-cli](https://github.com/cryptomator/hub-cli) to get the access token with Authorization Code flow: + +``` +hub login --client-id=cryptomator authorization-code --api-base $HUB_API_BASE | tee ACCESS_TOKEN.txt; export ACCESS_TOKEN=$(cat ACCESS_TOKEN.txt| tail -1) +``` + diff --git a/backend/pom.xml b/backend/pom.xml index e151de29d..35eded513 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,15 +5,13 @@ org.cryptomator hub-backend 1.4.0-SNAPSHOT - 3.11.0 UTF-8 17 UTF-8 - cryptomator - hub - 3.2.10.Final + shift7-ch + cipherduck3.2.10.Final eclipse-temurin:17-jre 4.4.0 3.1.2 @@ -138,6 +136,19 @@ io.quarkus quarkus-undertow + + org.apache.camel.quarkus + camel-quarkus-aws2-s3 + + + org.apache.camel.quarkus + camel-quarkus-aws2-iam + + + commons-io + commons-io + 2.13.0 + diff --git a/backend/setup/aws_static/aws_static_profile.json b/backend/setup/aws_static/aws_static_profile.json new file mode 100644 index 000000000..412a71261 --- /dev/null +++ b/backend/setup/aws_static/aws_static_profile.json @@ -0,0 +1,6 @@ +{ + "id": "72736C19-283C-49D3-80A5-AB74B5202543", + "name": "AWS S3 static", + "region": "eu-central-1", + "protocol": "S3" +} \ No newline at end of file diff --git a/backend/setup/aws_sts/aws_sts_profile.json b/backend/setup/aws_sts/aws_sts_profile.json new file mode 100644 index 000000000..f7b22da2f --- /dev/null +++ b/backend/setup/aws_sts/aws_sts_profile.json @@ -0,0 +1,16 @@ +{ + "id": "844BD517-96D4-4787-BCFA-238E103149F6", + "name": "AWS S3 STS", + "bucketPrefix": "cipherduck", + "stsRoleArnHub": "arn:aws:iam::930717317329:role/cipherduck-createbucket", + "stsRoleArnClient": "arn:aws:iam::930717317329:role/cipherduck-createbucket", + "region": "eu-west-1", + "regions": [ + "eu-west-1", + "eu-west-2", + "eu-west-3" + ], + "protocol": "S3STS", + "stsRoleArn": "arn:aws:iam::930717317329:role/cipherduck_chain_01", + "stsRoleArn2": "arn:aws:iam::930717317329:role/cipherduck_chain_02" +} \ No newline at end of file diff --git a/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json b/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json new file mode 100644 index 000000000..e0777a16f --- /dev/null +++ b/backend/setup/aws_sts/cipherduck_chain_01_permissionpolicy.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ], + "Resource": "arn:aws:iam::930717317329:role/cipherduck_chain_02" + } + ] +} \ No newline at end of file diff --git a/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json b/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json new file mode 100644 index 000000000..80b5d06de --- /dev/null +++ b/backend/setup/aws_sts/cipherduck_chain_01_trustpolicy.json @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": [ + "arn:aws:iam::930717317329:oidc-provider/login1.staging.cryptomator.cloud/realms/cipherduck", + "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck" + ] + }, + "Action": [ + "sts:AssumeRoleWithWebIdentity", + "sts:TagSession" + ], + "Condition": {} + } + ] +} \ No newline at end of file diff --git a/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json b/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json new file mode 100644 index 000000000..0b3c74709 --- /dev/null +++ b/backend/setup/aws_sts/cipherduck_chain_02_permissionpolicy.json @@ -0,0 +1,26 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:GetBucketVersioning" + ], + "Resource": "arn:aws:s3:::cipherduck${aws:PrincipalTag/VaultRequested}" + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload" + ], + "Resource": "arn:aws:s3:::cipherduck${aws:PrincipalTag/VaultRequested}/*" + } + ] +} \ No newline at end of file diff --git a/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json b/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json new file mode 100644 index 000000000..3b2292038 --- /dev/null +++ b/backend/setup/aws_sts/cipherduck_chain_02_trustpolicy.json @@ -0,0 +1,20 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::930717317329:role/cipherduck_chain_01" + }, + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ], + "Condition": { + "ForAnyValue:StringEquals": { + "sts:TransitiveTagKeys": "${aws:RequestTag/VaultRequested}" + } + } + } + ] +} \ No newline at end of file diff --git a/backend/setup/aws_sts/createbucketpermissionpolicy.json b/backend/setup/aws_sts/createbucketpermissionpolicy.json new file mode 100644 index 000000000..801f70985 --- /dev/null +++ b/backend/setup/aws_sts/createbucketpermissionpolicy.json @@ -0,0 +1,29 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:CreateBucket", + "s3:GetBucketPolicy", + "s3:PutBucketVersioning", + "s3:GetBucketVersioning", + "s3:GetAccelerateConfiguration", + "s3:PutAccelerateConfiguration", + "s3:GetEncryptionConfiguration", + "s3:PutEncryptionConfiguration" + ], + "Resource": "arn:aws:s3:::cipherduck*" + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::cipherduck*/vault.cryptomator", + "arn:aws:s3:::cipherduck*/*/" + ] + } + ] +} \ No newline at end of file diff --git a/backend/setup/aws_sts/createbuckettrustpolicy.json b/backend/setup/aws_sts/createbuckettrustpolicy.json new file mode 100644 index 000000000..6f31cb188 --- /dev/null +++ b/backend/setup/aws_sts/createbuckettrustpolicy.json @@ -0,0 +1,16 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": [ + "arn:aws:iam::930717317329:oidc-provider/testing.hub.cryptomator.org/kc/realms/cipherduck", + "arn:aws:iam::930717317329:oidc-provider/login1.staging.cryptomator.cloud/realms/cipherduck" + ] + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": {} + } + ] +} \ No newline at end of file diff --git a/backend/setup/minio_static/minio_static_profile.json b/backend/setup/minio_static/minio_static_profile.json new file mode 100644 index 000000000..5be63cd2a --- /dev/null +++ b/backend/setup/minio_static/minio_static_profile.json @@ -0,0 +1,9 @@ +{ + "id": "71B910E0-2ECC-46DE-A871-8DB28549677E", + "name": "MinIO S3 static", + "protocol": "S3", + "withPathStyleAccessEnabled": "true", + "hostname": "minio", + "port": "9000", + "scheme": "http" +} \ No newline at end of file diff --git a/backend/setup/minio_sts/accessbucketpolicy.json b/backend/setup/minio_sts/accessbucketpolicy.json new file mode 100644 index 000000000..09b5127b3 --- /dev/null +++ b/backend/setup/minio_sts/accessbucketpolicy.json @@ -0,0 +1,30 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:GetBucketVersioning", + "s3:ListBucket", + "s3:ListBucketMultipartUploads" + ], + "Resource": [ + "arn:aws:s3:::cipherduck${jwt:client_id}" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:GetObject", + "s3:ListMultipartUploadParts", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::cipherduck${jwt:client_id}/*" + ] + } + ] +} \ No newline at end of file diff --git a/backend/setup/minio_sts/createbucketpolicy.json b/backend/setup/minio_sts/createbucketpolicy.json new file mode 100644 index 000000000..7a205f25d --- /dev/null +++ b/backend/setup/minio_sts/createbucketpolicy.json @@ -0,0 +1,27 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:CreateBucket", + "s3:GetBucketPolicy", + "s3:PutBucketVersioning", + "s3:GetBucketVersioning" + ], + "Resource": [ + "arn:aws:s3:::cipherduck*/" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::cipherduck*/*/", + "arn:aws:s3:::cipherduck*/vault.cryptomator" + ] + } + ] +} \ No newline at end of file diff --git a/backend/setup/minio_sts/minio_sts_profile.json b/backend/setup/minio_sts/minio_sts_profile.json new file mode 100644 index 000000000..cc17b0575 --- /dev/null +++ b/backend/setup/minio_sts/minio_sts_profile.json @@ -0,0 +1,26 @@ +{ + "id": "732D43FA-3716-46C4-B931-66EA5405EF1C", + "name": "MinIO S3 STS", + "bucketPrefix": "cipherduck", + "region": "eu-central-1", + "regions": [ + "eu-west-1", + "eu-west-2", + "eu-west-3", + "eu-north-1", + "eu-south-1", + "eu-south-2", + "eu-central-1", + "eu-central-2" + ], + "withPathStyleAccessEnabled": "true", + "stsRoleArnHub": "arn:minio:iam:::role/HGKdlY4eFFsXVvJmwlMYMhmbnDE", + "stsRoleArnClient": "arn:minio:iam:::role/IqZpDC5ahW_DCAvZPZA4ACjEnDE", + "stsEndpoint": "http://minio:9000", + "protocol": "S3STS", + "scheme": "http", + "hostname": "minio", + "port": "9000", + "stsRoleArn": "arn:minio:iam:::role/Hdms6XDZ6oOpuWYI3gu4gmgHN94", + "bucketAcceleration": false +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java index dcdc6666b..2e1cf9fe6 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java @@ -4,7 +4,22 @@ import org.cryptomator.hub.entities.Authority; import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; - +import org.eclipse.microprofile.openapi.annotations.media.DiscriminatorMapping; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +// / start cipherduck extension +// TODO review: backport @Schema upstream? +@Schema( + title = "Authority", + oneOf = { UserDto.class, GroupDto.class, MemberDto.class }, + discriminatorMapping = { + @DiscriminatorMapping( value = "USER", schema = UserDto.class ), + @DiscriminatorMapping( value = "GROUP", schema = GroupDto.class ), + @DiscriminatorMapping( value = "MEMBER", schema = MemberDto.class ) + }, + discriminatorProperty = "type" +) +// \ end cipherduck extension abstract sealed class AuthorityDto permits UserDto, GroupDto, MemberDto { public enum Type { diff --git a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java index 5081b04cf..031ef28db 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/ConfigResource.java @@ -8,6 +8,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import org.cryptomator.hub.entities.Settings; import org.eclipse.microprofile.config.inject.ConfigProperty; import java.time.Instant; @@ -32,6 +33,12 @@ public class ConfigResource { @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-client-id", defaultValue = "") String keycloakClientIdCryptomator; + // / start cipherduck extension + @Inject + @ConfigProperty(name = "hub.keycloak.oidc.cryptomator-vaults-client-id", defaultValue = "") + String keycloakClientIdCryptomatorVaults; + // \ end cipherduck extension + @Inject @ConfigProperty(name = "quarkus.oidc.auth-server-url") String internalRealmUrl; @@ -49,9 +56,15 @@ public ConfigDto getConfig() { var authUri = replacePrefix(oidcConfData.getAuthorizationUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri); var tokenUri = replacePrefix(oidcConfData.getTokenUri(), trimTrailingSlash(internalRealmUrl), publicRealmUri); - return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 3); + return new ConfigDto(keycloakPublicUrl, keycloakRealm, keycloakClientIdHub, keycloakClientIdCryptomator, authUri, tokenUri, Instant.now().truncatedTo(ChronoUnit.MILLIS), 3 + // / start cipherduck extension + , keycloakClientIdCryptomatorVaults + , Settings.get().hubId + // \ end cipherduck extension + ); } + //visible for testing String replacePrefix(String str, String prefix, String replacement) { int index = str.indexOf(prefix); @@ -75,7 +88,12 @@ String trimTrailingSlash(String str) { public record ConfigDto(@JsonProperty("keycloakUrl") String keycloakUrl, @JsonProperty("keycloakRealm") String keycloakRealm, @JsonProperty("keycloakClientIdHub") String keycloakClientIdHub, @JsonProperty("keycloakClientIdCryptomator") String keycloakClientIdCryptomator, @JsonProperty("keycloakAuthEndpoint") String authEndpoint, @JsonProperty("keycloakTokenEndpoint") String tokenEndpoint, - @JsonProperty("serverTime") Instant serverTime, @JsonProperty("apiLevel") Integer apiLevel) { + @JsonProperty("serverTime") Instant serverTime, @JsonProperty("apiLevel") Integer apiLevel + // / start cipherduck extension + , @JsonProperty("keycloakClientIdCryptomatorVaults") String keycloakClientIdCryptomatorVaults + , @JsonProperty("uuid") String uuid + // \ end cipherduck extension + ) { } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java index 76d1e0c69..9d290b753 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -30,6 +30,8 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.cryptomator.hub.SyncerConfig; +import org.cryptomator.hub.api.cipherduck.CipherduckConfig; import org.cryptomator.hub.entities.AccessToken; import org.cryptomator.hub.entities.AuditEventVaultAccessGrant; import org.cryptomator.hub.entities.AuditEventVaultCreate; @@ -69,9 +71,15 @@ import java.util.UUID; import java.util.stream.Stream; +import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakGrantAccessToVault; +import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakRemoveAccessToVault; + @Path("/vaults") public class VaultResource { + @Inject + SyncerConfig syncerConfig; + @Inject JsonWebToken jwt; @@ -81,6 +89,11 @@ public class VaultResource { @Inject LicenseHolder license; + // / start cipherduck extension + @Inject + CipherduckConfig cipherduckConfig; + // \ end cipherduck extension + @GET @Path("/accessible") @RolesAllowed("user") @@ -167,6 +180,10 @@ public Response addUser(@PathParam("vaultId") UUID vaultId, @PathParam("userId") } } + // / start cipherduck extension + keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), userId, cipherduckConfig.keycloakClientIdCryptomatorVaults()); + // \ end cipherduck extension + return addAuthority(vault, user, role); } @@ -193,6 +210,10 @@ public Response addGroup(@PathParam("vaultId") UUID vaultId, @PathParam("groupId throw new PaymentRequiredException("Number of effective vault users greater than or equal to the available license seats"); } + // / start cipherduck extension + keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), groupId, cipherduckConfig.keycloakClientIdCryptomatorVaults()); + // \ end cipherduck extension + return addAuthority(vault, group, role); } @@ -227,6 +248,15 @@ private Response addAuthority(Vault vault, Authority authority, VaultAccess.Role @APIResponse(responseCode = "403", description = "not a vault owner") public Response removeAuthority(@PathParam("vaultId") UUID vaultId, @PathParam("authorityId") @ValidId String authorityId) { if (VaultAccess.deleteById(new VaultAccess.Id(vaultId, authorityId))) { + + // / start cipherduck extension + // Decision: when resetting an account or archiving a vault, access to the bucket doesn't need to be revoked. + // - Account reset: same situation as for addUser() and addGroup() before being granted access (masterkey): in the STS case, users can technically already gain access to the data at the storage level if they know/guess the STS endpoint etc, however they cannot decrypt yet. + // - Archiving: removeAuthority is not called in this case, so users still can renew access (get new temporary S3 credentials) at the storage level in the STS case. + // However, they cannot get the masterkey any more (in all cases) nor the permanent storage credentials (in the non-STS case). + keycloakRemoveAccessToVault(syncerConfig, vaultId.toString(), authorityId, "cryptomatorvaults"); + // \ end cipherduck extension + AuditEventVaultMemberRemove.log(jwt.getSubject(), vaultId, authorityId); return Response.status(Response.Status.NO_CONTENT).build(); } else { @@ -362,6 +392,7 @@ public Response grantAccess(@PathParam("vaultId") UUID vaultId, @NotEmpty Map getStorageProfiles(@Nullable @QueryParam("archived") Boolean archived) { + return StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).filter(p -> (null == archived) || (archived.booleanValue() == p.archived)).collect(Collectors.toList()); + } + + @GET + @Path("/s3") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends") + @APIResponse(responseCode = "200", description = "uploaded storage configuration") + @APIResponse(responseCode = "403", description = "not a user") + public List getStorageProfilesS3() { + return StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).filter(StorageProfileS3Dto.class::isInstance).map(StorageProfileS3Dto.class::cast).collect(Collectors.toList()); + } + + @GET + @Path("/{profileId}") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "gets a storage profile") + @APIResponse(responseCode = "200") + @APIResponse(responseCode = "403", description = "not a user") + public StorageProfileDto get(@PathParam("profileId") UUID profileId) { + return StorageProfileDto.fromEntity(StorageProfile.findByIdOptional(profileId).orElseThrow(NotFoundException::new)); + } + + @PUT + @Path("/{profileId}") + @RolesAllowed("admin") + @Transactional + @Produces(MediaType.APPLICATION_FORM_URLENCODED) + @Operation(summary = "archive a storage profile") + @APIResponse(responseCode = "204", description = "storage profile archived") + @APIResponse(responseCode = "403", description = "not an admin") + public Response archive(@PathParam("profileId") UUID profileId, @FormParam("archived") final boolean archived) { + final StorageProfile storageProfile = StorageProfile.findByIdOptional(profileId).orElseThrow(NotFoundException::new); + storageProfile.setArchived(archived).persistAndFlush(); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 refactor into uvf vault metadata + @GET + @Path("/meta") + @RolesAllowed("admin") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "get configs for storage backends", description = "get list of configs for storage backends") + @APIResponse(responseCode = "200", description = "uploaded storage configuration") + public VaultJWEPayloadDto getVaultJWEBackendDto(final StorageProfileDto.Protocol protocol) { + // N.B. temporary workaround to have VaultJWEBackendDto exposed in openapi.json for now.... + return null; + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java new file mode 100644 index 000000000..ebb25add1 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3Dto.java @@ -0,0 +1,91 @@ +package org.cryptomator.hub.api.cipherduck; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cryptomator.hub.entities.cipherduck.StorageProfileS3; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.util.UUID; + +public sealed class StorageProfileS3Dto extends StorageProfileDto permits StorageProfileS3STSDto { + + public enum S3_STORAGE_CLASSES { + STANDARD, INTELLIGENT_TIERING, STANDARD_IA, ONEZONE_IA, REDUCED_REDUNDANCY, GLACIER, GLACIER_IR, DEEP_ARCHIVE + } + + //====================================================================== + // (1) STS and permanent: + // - bucket creation frontend/desktop client (STS) + // - template upload (STS and permanent) + // - client profile (STS and permanent) + //====================================================================== + + @JsonProperty(value = "scheme", defaultValue = "https") + @Schema(description = "Scheme of S3 endpoint for template upload/bucket creation. Defaults to default for protocol, i.e. https in most cases.", example = "https", nullable = true) + String scheme; + + @JsonProperty("hostname") + @Schema(description = "Hostname S3 endpoint for template upload/bucket creation. Defaults to AWS SDK default.", example = "s3-us-gov-west-1.amazonaws.com", nullable = true) + String hostname; + + @JsonProperty("port") + @Schema(description = "Port S3 endpoint for template upload/bucket creation. Defaults to default port for scheme.", example = "443", nullable = true) + Integer port; + + @JsonProperty(value = "withPathStyleAccessEnabled") + @Schema(description = "Whether to use path style for S3 endpoint for template upload/bucket creation.", example = "false", defaultValue = "false") + Boolean withPathStyleAccessEnabled = false; + + @JsonProperty(value = "storageClass") + @Schema(description = "Storage class for upload. Defaults to STANDARD", example = "STANDARD", required = true) + S3_STORAGE_CLASSES storageClass = S3_STORAGE_CLASSES.STANDARD; + + public StorageProfileS3Dto() { + // jackson + } + + public StorageProfileS3Dto(final UUID id, final String name, final Protocol protocol, final boolean archived, final String scheme, final String hostname, final Integer port, final boolean withPathStyleAccessEnabled, final S3_STORAGE_CLASSES storageClass) { + super(id, name, protocol, archived); + this.scheme = scheme; + this.hostname = hostname; + this.port = port; + this.withPathStyleAccessEnabled = withPathStyleAccessEnabled; + this.storageClass = storageClass; + } + + static StorageProfileS3Dto fromEntity(final StorageProfileS3 storageProfile) { + return new StorageProfileS3Dto(storageProfile.id, storageProfile.name, Protocol.s3, storageProfile.archived, storageProfile.scheme, storageProfile.hostname, storageProfile.port, storageProfile.withPathStyleAccessEnabled, S3_STORAGE_CLASSES.valueOf(storageProfile.storageClass)); + } + + public StorageProfileS3 toEntity() { + final StorageProfileS3 storageProfile = new StorageProfileS3(); + storageProfile.id = this.id; + storageProfile.name = this.name; + storageProfile.archived = this.archived; + storageProfile.scheme = this.scheme; + storageProfile.hostname = this.hostname; + storageProfile.port = this.port; + storageProfile.withPathStyleAccessEnabled = this.withPathStyleAccessEnabled; + storageProfile.storageClass = this.storageClass.name(); + return storageProfile; + } + + public String scheme() { + return scheme; + } + + public String hostname() { + return hostname; + } + + public Integer port() { + return port; + } + + public Boolean withPathStyleAccessEnabled() { + return withPathStyleAccessEnabled; + } + + public S3_STORAGE_CLASSES storageClass() { + return storageClass; + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java new file mode 100644 index 000000000..6d5e6e34d --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageProfileS3STSDto.java @@ -0,0 +1,190 @@ +package org.cryptomator.hub.api.cipherduck; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cryptomator.hub.entities.cipherduck.StorageProfileS3STS; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import software.amazon.awssdk.regions.Region; + +import java.util.List; +import java.util.UUID; + +public final class StorageProfileS3STSDto extends StorageProfileS3Dto { + + public enum S3_SERVERSIDE_ENCRYPTION { + NONE, SSE_AES256, SSE_KMS_DEFAULT + } + + //====================================================================== + // (2) STS only: bucket creation + //====================================================================== + @JsonProperty(value = "region", required = true, defaultValue = "us-east-1") + @Schema(description = "Default region selected in the frontend/client to create bucket in.", example = "443", defaultValue = "us-east-1") + String region = "us-east-1"; + + @JsonProperty(value = "regions", required = true) + @Schema(description = "List of selectable regions in the frontend/client to create bucket in. Defaults to full list from AWS SDK.") + List regions = Region.regions().stream().map(Region::id).toList(); + + @JsonProperty(value = "bucketPrefix", required = true) + @Schema(description = "Buckets are create with name .", example = "cipherduck") + String bucketPrefix; + + @JsonProperty(value = "stsRoleArnClient", required = true) + @Schema(description = "STS role for clients to assume to create buckets. Will be the same as stsRoleArnHub for AWS, different for MinIO.", example = "arn:aws:iam:::role/cipherduck-createbucket") + String stsRoleArnClient; + + @JsonProperty(value = "stsRoleArnHub", required = true) + @Schema(description = "STS role for frontend to assume to create buckets (used with inline policy and passed to hub backend). Will be the same as stsRoleArnClient for AWS, different for MinIO.", example = "arn:aws:iam:::role/cipherduck-createbucket") + String stsRoleArnHub; + + @JsonProperty("stsEndpoint") + @Schema(description = "STS endpoint to use for AssumeRoleWithWebIdentity and AssumeRole for getting a temporary access token passed to the backend. Defaults to AWS SDK default.", nullable = true) + String stsEndpoint; + + @JsonProperty(value = "bucketVersioning", defaultValue = "true", required = true) + @Schema(description = "Enable bucket versioning upon bucket creation", defaultValue = "true", required = true) + Boolean bucketVersioning = true; + + @JsonProperty(value = "bucketAcceleration", defaultValue = "true") + @Schema(description = "Enable bucket versioning upon bucket creation", defaultValue = "true", required = true) + Boolean bucketAcceleration = true; + + @JsonProperty(value = "bucketEncryption", required = true) + @Schema(description = "Enable bucket versioning upon bucket creation", required = true) + S3_SERVERSIDE_ENCRYPTION bucketEncryption = S3_SERVERSIDE_ENCRYPTION.NONE; + + //---------------------------------------------------------------------- + // (3b) STS client profile custom properties + //---------------------------------------------------------------------- + @JsonProperty(value = "stsRoleArn", required = true) + @Schema(description = "roleArn to for STS AssumeRoleWithWebIdentity (AWS and MinIO)", example = "arn:aws:iam::930717317329:role/cipherduck_chain_01") + String stsRoleArn; + + @JsonProperty(value = "stsRoleArn2") + @Schema(description = "roleArn to assume for STS AssumeRole in role chaining (AWS only, not MinIO)", example = "arn:aws:iam::930717317329:role/cipherduck_chain_02", nullable = true) + String stsRoleArn2; + + + @JsonProperty(value = "stsDurationSeconds", required = false) + @Schema(description = "Token lifetime for STS tokens assumed. Defaults to AWS/MinIO defaults", nullable = true) + Integer stsDurationSeconds; + + public StorageProfileS3STSDto() { + // jackson + } + + public StorageProfileS3STSDto(final UUID id, final String name, final Protocol protocol, final boolean archived, final String scheme, final String hostname, final Integer port, final boolean withPathStyleAccessEnabled, final S3_STORAGE_CLASSES storageClass, final String region, final List regions, final String bucketPrefix, final String stsRoleArnClient, final String stsRoleArnHub, final String stsEndpoint, final boolean bucketVersioning, final boolean bucketAcceleration, final S3_SERVERSIDE_ENCRYPTION bucketEncryption, final String stsRoleArn, final String stsRoleArn2, final Integer stsDurationSeconds) { + super(id, name, protocol, archived, scheme, hostname, port, withPathStyleAccessEnabled, storageClass); + this.region = region; + this.regions = regions; + this.bucketPrefix = bucketPrefix; + this.stsRoleArnClient = stsRoleArnClient; + this.stsRoleArnHub = stsRoleArnHub; + this.stsEndpoint = stsEndpoint; + this.bucketVersioning = bucketVersioning; + this.bucketAcceleration = bucketAcceleration; + this.bucketEncryption = bucketEncryption; + this.stsRoleArn = stsRoleArn; + this.stsRoleArn2 = stsRoleArn2; + this.stsDurationSeconds = stsDurationSeconds; + } + + static StorageProfileS3STSDto fromEntity(final StorageProfileS3STS storageProfile) { + return new StorageProfileS3STSDto( + storageProfile.id, + storageProfile.name, + Protocol.s3sts, + storageProfile.archived, + storageProfile.scheme, + storageProfile.hostname, + storageProfile.port, + storageProfile.withPathStyleAccessEnabled, + S3_STORAGE_CLASSES.valueOf(storageProfile.storageClass), + storageProfile.region, + storageProfile.regions, + storageProfile.bucketPrefix, + storageProfile.stsRoleArnClient, + storageProfile.stsRoleArnHub, + storageProfile.stsEndpoint, + storageProfile.bucketVersioning, + storageProfile.bucketAcceleration, + S3_SERVERSIDE_ENCRYPTION.valueOf(storageProfile.bucketEncryption), + storageProfile.stsRoleArn, + storageProfile.stsRoleArn2, + storageProfile.stsDurationSeconds + ); + } + + public StorageProfileS3STS toEntity() { + final StorageProfileS3STS storageProfile = new StorageProfileS3STS(); + storageProfile.id = this.id; + storageProfile.name = this.name; + storageProfile.archived = this.archived; + storageProfile.scheme = this.scheme; + storageProfile.hostname = this.hostname; + storageProfile.port = this.port; + storageProfile.withPathStyleAccessEnabled = this.withPathStyleAccessEnabled; + storageProfile.storageClass = this.storageClass.toString(); + storageProfile.region = this.region; + storageProfile.regions = this.regions; + storageProfile.bucketPrefix = this.bucketPrefix; + storageProfile.stsRoleArnClient = this.stsRoleArnClient; + storageProfile.stsRoleArnHub = this.stsRoleArnHub; + storageProfile.stsEndpoint = this.stsEndpoint; + storageProfile.bucketVersioning = this.bucketVersioning; + storageProfile.bucketAcceleration = this.bucketAcceleration; + storageProfile.bucketEncryption = this.bucketEncryption.name(); + storageProfile.stsRoleArn = this.stsRoleArn; + storageProfile.stsRoleArn2 = this.stsRoleArn2; + storageProfile.stsDurationSeconds = this.stsDurationSeconds; + return storageProfile; + } + + public String region() { + return region; + } + + public List regions() { + return regions; + } + + public String bucketPrefix() { + return bucketPrefix; + } + + public String stsRoleArnClient() { + return stsRoleArnClient; + } + + public String stsRoleArnHub() { + return stsRoleArnHub; + } + + public String stsEndpoint() { + return stsEndpoint; + } + + public Boolean bucketVersioning() { + return bucketVersioning; + } + + public Boolean bucketAcceleration() { + return bucketAcceleration; + } + + public S3_SERVERSIDE_ENCRYPTION bucketEncryption() { + return bucketEncryption; + } + + public String stsRoleArn() { + return stsRoleArn; + } + + public String stsRoleArn2() { + return stsRoleArn2; + } + + public Integer stsDurationSeconds() { + return stsDurationSeconds; + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java new file mode 100644 index 000000000..f609bfab2 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/StorageResource.java @@ -0,0 +1,85 @@ +package org.cryptomator.hub.api.cipherduck; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.cryptomator.hub.SyncerConfig; +import org.cryptomator.hub.api.GoneException; +import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.entities.Vault; +import org.cryptomator.hub.entities.cipherduck.StorageProfile; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.jboss.logging.Logger; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.cryptomator.hub.api.cipherduck.storage.S3StorageHelper.makeS3Bucket; +import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakGrantAccessToVault; +import static org.cryptomator.hub.cipherduck.KeycloakCryptomatorVaultsHelper.keycloakPrepareVault; + +@Path("/storage") +public class StorageResource { + private static final Logger LOG = Logger.getLogger(StorageResource.class); + + @Inject + SyncerConfig syncerConfig; + + @Inject + CipherduckConfig cipherduckConfig; + + @Inject + JsonWebToken jwt; + + + @PUT + @Path("/{vaultId}") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "creates bucket and policy", description = "creates an S3 bucket and uploads policy for it.") + @APIResponse(responseCode = "200", description = "Bucket and Keycloak config created") + @APIResponse(responseCode = "400", description = "Could not create bucket") + @APIResponse(responseCode = "409", description = "Vault with this ID or bucket with this name already exists") + @APIResponse(responseCode = "410", description = "Storage profile is archived") + public Response createBucket(@PathParam("vaultId") UUID vaultId, final StorageDto storage) { + Optional vault = Vault.findByIdOptional(vaultId); + if (vault.isPresent()) { + throw new ClientErrorException(String.format("Vault with ID %s already exists", vaultId), Response.Status.CONFLICT); + } + + final Map storageConfigs = StorageProfile.findAll().stream().map(StorageProfileDto::fromEntity).collect(Collectors.toMap(StorageProfileDto::id, Function.identity())); + if (!storageConfigs.containsKey(storage.storageConfigId())) { + return Response.status(Response.Status.BAD_REQUEST).entity(String.format("Storage profile %s not found on this server", storage.storageConfigId())).build(); + } + final StorageProfileDto storageProfileDto = storageConfigs.get(storage.storageConfigId()); + if (storageProfileDto.archived) { + throw new GoneException("Storage profile is archived."); + } + if (!(storageProfileDto instanceof StorageProfileS3STSDto)) { + return Response.status(Response.Status.BAD_REQUEST).entity(String.format("Storage profile must be StorageProfileS3STSDto. Found %s", storageProfileDto.getClass().getName())).build(); + } + + // N.B. if the bucket already exists, this will fail, so we do not prevent calling this method several times. + makeS3Bucket((StorageProfileS3STSDto) storageProfileDto, storage); + + final User currentUser = User.findById(jwt.getSubject()); + keycloakGrantAccessToVault(syncerConfig, vaultId.toString(), currentUser.id, cipherduckConfig.keycloakClientIdCryptomatorVaults()); + keycloakPrepareVault(syncerConfig, vaultId.toString(), (StorageProfileS3STSDto) storageProfileDto, jwt.getSubject(), cipherduckConfig.keycloakClientIdCryptomatorVaults()); + + return Response.created(URI.create(".")).build(); + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java new file mode 100644 index 000000000..ff391e263 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEBackendDto.java @@ -0,0 +1,30 @@ +package org.cryptomator.hub.api.cipherduck; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of vault JWE specifying the vault bookmark. + * Allows to create a bookmark in the client referencing the vendor in the storage profiles. + * This Java record is unused in hub, only its ts counterpart in `backend.ts`. + * It will used in Cipherduck client in the OpenAPI generator. + */ +public record VaultJWEBackendDto( + + @JsonProperty(value = "provider", required = true) + // references id in StorageProfileDto (aka. vendor in client profile) + String provider, + @JsonProperty(value = "defaultPath", required = true) + String defaultPath, + @JsonProperty(value = "nickname", required = true) + String nickname, + @JsonProperty(value = "region", required = true) + String region, + + @JsonProperty(value = "username") + // for non-STS + String username, + @JsonProperty(value = "password") + // for non-STS + String password +) { +} \ No newline at end of file diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java new file mode 100644 index 000000000..a3b7dcb85 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/VaultJWEPayloadDto.java @@ -0,0 +1,17 @@ +package org.cryptomator.hub.api.cipherduck; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record VaultJWEPayloadDto( + + @JsonProperty(value = "key", required = true) + // masterkey + String key, + + @JsonProperty(value = "backend", required = true) + VaultJWEBackendDto backend, + + @JsonProperty(value = "automaticAccessGrant", required = true) + AutomaticAccessGrant automaticAccessGrant +) { +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java new file mode 100644 index 000000000..3f01b1a34 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/api/cipherduck/storage/S3StorageHelper.java @@ -0,0 +1,205 @@ +package org.cryptomator.hub.api.cipherduck.storage; + + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.core.Response; +import org.cryptomator.hub.api.cipherduck.CreateS3STSBucketDto; +import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto; +import org.jboss.logging.Logger; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.AccelerateConfiguration; +import software.amazon.awssdk.services.s3.model.BucketAccelerateStatus; +import software.amazon.awssdk.services.s3.model.BucketVersioningStatus; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationRequest; +import software.amazon.awssdk.services.s3.model.GetBucketAccelerateConfigurationResponse; +import software.amazon.awssdk.services.s3.model.GetBucketEncryptionRequest; +import software.amazon.awssdk.services.s3.model.GetBucketEncryptionResponse; +import software.amazon.awssdk.services.s3.model.GetBucketVersioningRequest; +import software.amazon.awssdk.services.s3.model.GetBucketVersioningResponse; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.NoSuchBucketException; +import software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest; +import software.amazon.awssdk.services.s3.model.PutBucketEncryptionRequest; +import software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.model.ServerSideEncryption; +import software.amazon.awssdk.services.s3.model.ServerSideEncryptionByDefault; +import software.amazon.awssdk.services.s3.model.ServerSideEncryptionConfiguration; +import software.amazon.awssdk.services.s3.model.ServerSideEncryptionRule; +import software.amazon.awssdk.services.s3.model.VersioningConfiguration; + +import java.net.URI; +import java.util.Collections; + +public class S3StorageHelper { + private static final Logger log = Logger.getLogger(S3StorageHelper.class); + + public static void makeS3Bucket( + final StorageProfileS3STSDto storageConfig, + final CreateS3STSBucketDto dto + ) { + + if (log.isInfoEnabled()) { + log.info(String.format("Make S3 bucket %s for profile %s", dto, storageConfig)); + } + + final String bucketName = storageConfig.bucketPrefix() + dto.vaultId(); + // https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/java/example_code/s3/src/main/java/aws/example/s3/CreateBucket.java + final String region = dto.region(); + + S3ClientBuilder s3Builder = S3Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsSessionCredentials.create(dto.awsAccessKey(), dto.awsSecretKey(), dto.sessionToken()))); + if (storageConfig.stsEndpoint() != null) { + s3Builder = s3Builder + .endpointOverride(URI.create(storageConfig.stsEndpoint())) + .serviceConfiguration(S3Configuration.builder() + .pathStyleAccessEnabled(storageConfig.withPathStyleAccessEnabled() != null ? storageConfig.withPathStyleAccessEnabled() : false) + .build()); + } else if (region != null) { + s3Builder = s3Builder.region(Region.of(region)); + } + final S3Client s3 = s3Builder.build(); + boolean okToCreate = true; + try { + s3.headBucket(HeadBucketRequest.builder().bucket(bucketName).build()); + } catch (final NoSuchBucketException e) { + okToCreate = true; + } catch (final S3Exception e) { + if (e.statusCode() == 403) { + // ignore + log.info("Ignoring 403 on bucket head", e); + okToCreate = true; + } + } + if (!okToCreate) { + throw new ClientErrorException(String.format("Bucket %s already exists or no permission to list.", bucketName), Response.Status.CONFLICT); + } + + s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); + if (log.isInfoEnabled()) { + log.info(String.format("Upload vault template to %s (%s, %s)", bucketName, dto, storageConfig)); + } + s3.putObject(PutObjectRequest.builder() + .bucket(bucketName) + .key("vault.uvf") + .build(), + RequestBody.fromString(dto.vaultUvf())); + + // See https://github.com/cryptomator/hub/blob/develop/frontend/src/common/vaultconfig.ts + // zip.file('vault.uvf', this.vaultUvf); + // zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2)); + // create meta-data for your folder and set content-length to 0 + final PutObjectRequest request2 = PutObjectRequest.builder() + .bucket(bucketName) + .key(String.format("d/%s/%s/", dto.rootDirHash().substring(0, 2), dto.rootDirHash().substring(2))) + .contentLength(0L) + .build(); + s3.putObject(request2, RequestBody.empty()); + + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 CORS? should we allow for not setting (because of permissions)? E.g. MinIO which has now bucket acceleration + // enable versioning on the bucket. + { + if (log.isInfoEnabled()) { + log.info(String.format("Enable/disable bucket versioning on %s (%s, %s)", bucketName, dto, storageConfig)); + } + s3.putBucketVersioning(PutBucketVersioningRequest.builder() + .bucket(bucketName) + .versioningConfiguration(VersioningConfiguration.builder().status(storageConfig.bucketVersioning() ? BucketVersioningStatus.ENABLED : BucketVersioningStatus.SUSPENDED).build()) + .build()); + final GetBucketVersioningResponse conf = s3.getBucketVersioning(GetBucketVersioningRequest.builder().bucket(bucketName).build()); + if (log.isInfoEnabled()) { + log.info(String.format("Enabled/disabled bucket versioning on %s (%s, %s) with status %s", bucketName, dto, storageConfig, conf.statusAsString())); + } + } + + // enable/disable bucket acceleration on the bucket + { + if (log.isInfoEnabled()) { + log.info(String.format("Enable/disable bucket acceleration on %s (%s, %s)", bucketName, dto, storageConfig)); + } + try { + s3.putBucketAccelerateConfiguration(PutBucketAccelerateConfigurationRequest.builder() + .bucket(bucketName) + .accelerateConfiguration(AccelerateConfiguration.builder() + .status(storageConfig.bucketAcceleration() ? BucketAccelerateStatus.ENABLED : BucketAccelerateStatus.SUSPENDED) + .build()) + .build()); + } catch (S3Exception e) { + if (!storageConfig.bucketAcceleration() && e.awsErrorDetails().errorCode().equals("MalformedXML")) { + // MinIO does not support bucket acceleration nor encryption -> TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 should we make bucketAcceleration attribute in storage profile nullable instead? + // https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html + // https://github.com/minio/minio/blob/master/cmd/api-router.go + // https://github.com/minio/minio/issues/14586 + log.warn(String.format("Ignoring failed SetBucketAccelerateConfiguration call - MinIO does not support it. Details: %s", e)); + } + else { + throw e; + } + } + + final GetBucketAccelerateConfigurationResponse conf = s3.getBucketAccelerateConfiguration(GetBucketAccelerateConfigurationRequest.builder().bucket(bucketName).build()); + if (log.isInfoEnabled()) { + log.info(String.format("Enabled/disabled bucket acceleration on %s (%s, %s) with status %s", bucketName, dto, storageConfig, conf.status())); + } + } + + // enable/disable bucket encryption on the bucket + { + if (log.isInfoEnabled()) { + log.info(String.format("Enable/disable bucket encryption on %s (%s, %s)", bucketName, dto, storageConfig)); + } + switch (storageConfig.bucketEncryption()) { + case NONE -> { + } + case SSE_AES256 -> s3.putBucketEncryption( + PutBucketEncryptionRequest.builder() + .bucket(bucketName) + .serverSideEncryptionConfiguration(ServerSideEncryptionConfiguration.builder() + .rules(Collections.singleton( + ServerSideEncryptionRule.builder() + .applyServerSideEncryptionByDefault(ServerSideEncryptionByDefault.builder() + .sseAlgorithm(ServerSideEncryption.AES256).build()) + .build())) + + .build()) + .build()); + case SSE_KMS_DEFAULT -> s3.putBucketEncryption( + PutBucketEncryptionRequest.builder() + .bucket(bucketName) + .serverSideEncryptionConfiguration(ServerSideEncryptionConfiguration.builder() + .rules(Collections.singleton( + ServerSideEncryptionRule.builder() + .applyServerSideEncryptionByDefault(ServerSideEncryptionByDefault.builder() + .sseAlgorithm(ServerSideEncryption.AWS_KMS).build()) + .build())) + + .build()) + .build()); + } + switch (storageConfig.bucketEncryption()) { + case NONE: + // MinIO does not support bucket acceleration nor encryption -> TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 should we make bucketEncryption attribute in storage profile nullable instead? + // https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html + // https://github.com/minio/minio/blob/master/cmd/api-router.go + // https://github.com/minio/minio/issues/14586 + break; + case SSE_AES256: + case SSE_KMS_DEFAULT: + final GetBucketEncryptionResponse conf = s3.getBucketEncryption(GetBucketEncryptionRequest.builder().bucket(bucketName).build()); + if (log.isInfoEnabled()) { + log.info(String.format("Enabled/disabled bucket encryption on %s (%s, %s) with configuration %s", bucketName, dto, storageConfig, conf.serverSideEncryptionConfiguration())); + } + } + } + // TODO https://github.com/shift7-ch/cipherduck-hub/issues/44 CORS? + //s3.setBucketCrossOriginConfiguration(); + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java new file mode 100644 index 000000000..7219818e9 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsHelper.java @@ -0,0 +1,235 @@ +package org.cryptomator.hub.cipherduck; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.core.Response; +import org.cryptomator.hub.SyncerConfig; +import org.cryptomator.hub.api.VaultResource; +import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto; +import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.Vault; +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.ClientWebApplicationException; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientScopeResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@ApplicationScoped +public class KeycloakCryptomatorVaultsHelper { + + private static final Logger LOG = Logger.getLogger(KeycloakCryptomatorVaultsHelper.class); + + public static void keycloakPrepareVault(final SyncerConfig syncerConfig, final String vaultId, final StorageProfileS3STSDto storageConfig, final String userOrGroupId, final String clientId) { + + // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765 + // TODO review better solution than using sentinel string "empty"? + if ("empty".equals(syncerConfig.getKeycloakUrl())) { + LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId)); + return; + } + + try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) { + + // https://www.keycloak.org/docs-api/21.1.1/rest-api + final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm()); + + final boolean minio = storageConfig.stsRoleArn() != null && storageConfig.stsRoleArn2() == null; + final boolean aws = storageConfig.stsRoleArn() != null && storageConfig.stsRoleArn2() != null; + + ClientScopeResource clientScopeResource = realm.clientScopes().get(vaultId); + + if (minio) { + ProtocolMapperRepresentation minioProtocolMapper = new ProtocolMapperRepresentation(); + minioProtocolMapper.setName(String.format("Hard-coded mapper for vault %s (MinIO)", vaultId)); + minioProtocolMapper.setProtocolMapper("oidc-hardcoded-claim-mapper"); + minioProtocolMapper.setProtocol("openid-connect"); + + Map minioConfig = new HashMap<>(); + minioConfig.put("jsonType.label", "String"); + + minioConfig.put("userinfo.token.claim", "false"); + minioConfig.put("id.token.claim", "false"); + minioConfig.put("access.token.claim", "true"); + minioConfig.put("access.tokenResponse.claim", "false"); + + // exhaustive list of jwt claims evaluated in MinIO: https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html#policy-variables + // let's use client_id, as aud etc. are already use by standard mappers + minioConfig.put("claim.name", "client_id"); + minioConfig.put("claim.value", vaultId); + + minioProtocolMapper.setConfig(minioConfig); + + clientScopeResource.getProtocolMappers().createMapper(Arrays.asList(minioProtocolMapper)); + } + if (aws) { + ProtocolMapperRepresentation awsProtocolMapper = new ProtocolMapperRepresentation(); + awsProtocolMapper.setName(String.format("Hard-coded mapper for vault %s (AWS)", vaultId)); + awsProtocolMapper.setProtocolMapper("oidc-hardcoded-claim-mapper"); + awsProtocolMapper.setProtocol("openid-connect"); + + Map awsConfig = new HashMap<>(); + awsConfig.put("jsonType.label", "JSON"); + + awsConfig.put("userinfo.token.claim", "false"); + awsConfig.put("id.token.claim", "false"); + awsConfig.put("access.token.claim", "true"); + awsConfig.put("access.tokenResponse.claim", "false"); + + awsConfig.put("claim.name", "https://aws\\.amazon\\.com/tags"); + awsConfig.put("claim.value", String.format("{\"principal_tags\":{\"%s\":[\"\"]},\"transitive_tag_keys\":[\"%s\"]}", vaultId, vaultId)); + + awsProtocolMapper.setConfig(awsConfig); + + clientScopeResource.getProtocolMappers().createMapper(Arrays.asList(awsProtocolMapper)); + } + } + } + + public static void keycloakGrantAccessToVault(final SyncerConfig syncerConfig, final String vaultId, final String userOrGroupId, final String clientId) { + // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765 + // TODO review better solution than using sentinel string "empty"? + if ("empty".equals(syncerConfig.getKeycloakUrl())) { + LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId)); + return; + } + + var group = Group.findByIdOptional(userOrGroupId); + final boolean isGroup = group.isPresent(); + + try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) { + + // https://www.keycloak.org/docs-api/21.1.1/rest-api + final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm()); + + List byClientId = realm.clients().findByClientId(clientId); + if (byClientId.size() != 1) { + throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId)); + } + final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0); + ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId()); + + // create client scope (if necessary) + if (realm.clientScopes().findAll().stream().map(clientScopeRepresentation -> clientScopeRepresentation.getId()).noneMatch(vaultId::equals)) { + ClientScopeRepresentation vaultClientScope = new ClientScopeRepresentation(); + vaultClientScope.setId(vaultId); + vaultClientScope.setName(vaultId); + vaultClientScope.setDescription(String.format("Client scope for vault %s", vaultId)); + vaultClientScope.setAttributes(new HashMap<>()); + vaultClientScope.setProtocol("openid-connect"); + + Response response = realm.clientScopes().create(vaultClientScope); + if (response.getStatus() != 201) { + throw new RuntimeException(String.format("Failed to create client for vault %s. %s", vaultId, response.getStatusInfo().getReasonPhrase())); + } + } + + // add client scope to "cryptomatorvaults" client + // -> requires role_manage-clients + cryptomatorVaultsClientResource.addOptionalClientScope(vaultId); + + + // create client role (if necessary) + // -> requires role_manage-clients + if (cryptomatorVaultsClientResource.roles().list().stream().map(role -> role.getName()).noneMatch(vaultId::equals)) { + RoleRepresentation vaultRole = new RoleRepresentation(); + vaultRole.setName(vaultId); + vaultRole.setDescription(String.format("Role for vault %s", vaultId)); + vaultRole.setClientRole(true); + + cryptomatorVaultsClientResource.roles().create(vaultRole); + } + + // scope the client scope to the client role for the vault + realm.clientScopes().get(vaultId).getScopeMappings().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation())); + + + // add client role to user/group + // -> requires role_manage-users + if (!isGroup) { + realm.users().get(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation())); + } else { + realm.groups().group(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).add(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation())); + } + } + } + + + public static void keycloakRemoveAccessToVault(final SyncerConfig syncerConfig, final String vaultId, final String userOrGroupId, final String clientId) { + // N.B. quarkus has no means to provide empty string as value, interpreted as no value, see https://github.com/quarkusio/quarkus/issues/2765 + // TODO review better solution than using sentinel string "empty"? + if ("empty".equals(syncerConfig.getKeycloakUrl())) { + LOG.error(String.format("Could not grant access to vault %s for user %s as keycloak URL is not defined.", vaultId, userOrGroupId)); + return; + } + + final boolean isGroup = Group.findByIdOptional(userOrGroupId).isPresent(); + + try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) { + + // https://www.keycloak.org/docs-api/21.1.1/rest-api + final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm()); + + // add client scope to "cryptomatorvaults" client + // -> requires role_manage-clients + List byClientId = realm.clients().findByClientId(clientId); + if (byClientId.size() != 1) { + throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId)); + } + final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0); + ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId()); + cryptomatorVaultsClientResource.addOptionalClientScope(vaultId); + + // remove client role from user/group + // -> requires role_manage-users + if (!isGroup) { + realm.users().get(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).remove(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation())); + } else { + realm.groups().group(userOrGroupId).roles().clientLevel(cryptomatorVaultsClientRepresentation.getId()).remove(List.of(cryptomatorVaultsClientResource.roles().get(vaultId).toRepresentation())); + } + } + } + + // TODO review: this loop might not be safe enough to run in production - should we just disable this feature or remove from code entirely? + // Deleting the cryptomatorvaults client also deletes the client roles under the client, however, the client scopes are at the realm level and will not be removed by this procedure. + // Although safe, this can quickly become a mess in developing scenarios. + public static void keycloakCleanupDanglingCryptomatorVaultsRoles(final SyncerConfig syncerConfig, final String clientId) { + Set existingVaultIds = Vault.findAll().stream().map(VaultResource.VaultDto::fromEntity).map(vdto -> vdto.id().toString()).collect(Collectors.toSet()); + try (final Keycloak keycloak = Keycloak.getInstance(syncerConfig.getKeycloakUrl(), syncerConfig.getKeycloakRealm(), syncerConfig.getUsername(), syncerConfig.getPassword(), syncerConfig.getKeycloakClientId())) { + + // https://www.keycloak.org/docs-api/21.1.1/rest-api + final RealmResource realm = keycloak.realm(syncerConfig.getKeycloakRealm()); + + List byClientId = realm.clients().findByClientId(clientId); + if (byClientId.size() != 1) { + throw new RuntimeException(String.format("There are %s clients with clientId %s, expected to found exactly one.", byClientId.size(), clientId)); + } + final ClientRepresentation cryptomatorVaultsClientRepresentation = byClientId.get(0); + ClientResource cryptomatorVaultsClientResource = realm.clients().get(cryptomatorVaultsClientRepresentation.getId()); + + for (RoleRepresentation roleRepresentation : cryptomatorVaultsClientResource.roles().list()) { + final String vaultId = roleRepresentation.getName(); + if (!existingVaultIds.contains(vaultId)) { + cryptomatorVaultsClientResource.roles().deleteRole(vaultId); + try { + realm.clientScopes().get(vaultId).remove(); + } catch (ClientWebApplicationException e) { + if (LOG.isInfoEnabled()) { + LOG.info(String.format("Could not delete client scope %s", vaultId), e); + } + } + } + } + } + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java new file mode 100644 index 000000000..addc6574b --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/cipherduck/KeycloakCryptomatorVaultsSyncer.java @@ -0,0 +1,24 @@ +package org.cryptomator.hub.cipherduck; + + +import io.quarkus.scheduler.Scheduled; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.cryptomator.hub.SyncerConfig; +import org.cryptomator.hub.api.cipherduck.CipherduckConfig; + +@ApplicationScoped +public class KeycloakCryptomatorVaultsSyncer { + + @Inject + SyncerConfig syncerConfig; + + @Inject + CipherduckConfig cipherduckConfig; + + @Scheduled(every = "{hub.keycloak.syncer-period}") + void sync() { + KeycloakCryptomatorVaultsHelper.keycloakCleanupDanglingCryptomatorVaultsRoles(syncerConfig, cipherduckConfig.keycloakClientIdCryptomatorVaults()); + } + +} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java new file mode 100644 index 000000000..dc9fbf44a --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfile.java @@ -0,0 +1,33 @@ +package org.cryptomator.hub.entities.cipherduck; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; + +import java.util.UUID; + +@Entity +@Table(name = "storage_profile") +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn(name = "protocol") +public class StorageProfile extends PanacheEntityBase { // TODO make sealed? + @Id + @Column(name = "id", nullable = false) + public UUID id; + + @Column(name = "name", nullable = false) + public String name; + + @Column(name = "archived", nullable = false) + public boolean archived; + + public StorageProfile setArchived(boolean archived) { + this.archived = archived; + return this; + } +} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java new file mode 100644 index 000000000..711238a9e --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3.java @@ -0,0 +1,35 @@ +package org.cryptomator.hub.entities.cipherduck; + +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +import java.util.UUID; + +@Entity +@Table(name = "storage_profile_s3") +@DiscriminatorValue("S3") +public class StorageProfileS3 extends StorageProfile {// TODO make sealed? + + //====================================================================== + // (1) STS and permanent: + // - bucket creation frontend/desktop client (STS) + // - template upload (STS and permanent) + // - client profile (STS and permanent) + //====================================================================== + @Column + public String scheme; + + @Column + public String hostname; + + @Column + public Integer port; + + @Column + public Boolean withPathStyleAccessEnabled = false; + + @Column + public String storageClass = "STANDARD"; +} diff --git a/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java new file mode 100644 index 000000000..c098fbc65 --- /dev/null +++ b/backend/src/main/java/org/cryptomator/hub/entities/cipherduck/StorageProfileS3STS.java @@ -0,0 +1,61 @@ +package org.cryptomator.hub.entities.cipherduck; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import org.cryptomator.hub.api.cipherduck.StorageProfileS3STSDto; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "storage_profile_s3_sts") +@DiscriminatorValue("S3STS") +public class StorageProfileS3STS extends StorageProfileS3 { // TODO make sealed/final? + + //====================================================================== + // (2) STS only: bucket creation + //====================================================================== + @Column + public String region; + + @Column + public List regions; + + @Column + public String bucketPrefix; + + @Column + public String stsRoleArnClient; + + @Column + public String stsRoleArnHub; + + @Column + public String stsEndpoint = null; + + @Column + public Boolean bucketVersioning = true; + + @Column + public Boolean bucketAcceleration = true; + + @Column + public String bucketEncryption; + + //---------------------------------------------------------------------- + // (3b) STS client profile custom properties + //---------------------------------------------------------------------- + @Column + public String stsRoleArn; + + @Column + public String stsRoleArn2; + + @Column + public Integer stsDurationSeconds = null; + +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 76d81e38d..5355be9c6 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -13,6 +13,10 @@ hub.public-root-path=${quarkus.http.root-path} # `public-url` is used in the frontend (js), `local-url` in the backend. Maybe the same URL, but does not have to be. hub.keycloak.public-url=http://localhost:8180 hub.keycloak.local-url=http://localhost:8180 + +# TODO review better solution than using sentinel string "empty"?, see KeycloakGrantAccessToVault:keycloakGrantAccessToVault +%test.hub.keycloak.local-url=empty + hub.keycloak.realm=cryptomator hub.managed-instance=false @@ -21,16 +25,25 @@ quarkus.resteasy-reactive.path=/api %test.quarkus.resteasy-reactive.path=/ quarkus.http.port=8080 +quarkus.http.access-log.enabled=true +%dev.quarkus.log.level=DEBUG quarkus.oidc.application-type=service quarkus.oidc.client-id=cryptomatorhub hub.keycloak.oidc.cryptomator-client-id=cryptomator +hub.keycloak.oidc.cryptomator-vaults-client-id=cryptomatorvaults + # Keycloak dev service %dev.quarkus.keycloak.devservices.realm-path=dev-realm.json # TODO: realm-path needs to be in class path, i.e. under src/main/resources -> we might not want to include it in production jar though, so make use of maven profiles and specify optional resources https://github.com/quarkusio/quarkus-quickstarts/blob/f3f4939df30bcff062be126faaaeb58cb7c79fb6/security-keycloak-authorization-quickstart/pom.xml#L68-L75 %dev.quarkus.keycloak.devservices.realm-name=cryptomator -%dev.quarkus.keycloak.devservices.start-command=start-dev +# TODO review add to Dockerfile as well? +# https://github.com/quarkusio/quarkus/blob/596d9ae7a76cf529d24594a82b7c540030799dac/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java#L30 +# https://github.com/quarkusio/quarkus/blob/main/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java#L110 +# https://github.com/cryptomator/cryptomator.github.io/blob/52ac36a1db04ce1aa6db41f0aeb0a0f2b76b68b5/assets/js/hubsetup.js#L112 +# https://github.com/keycloak/keycloak/issues/20582 +%dev.quarkus.keycloak.devservices.start-command=start-dev --import-realm --features=token-exchange,admin-fine-grained-authz %dev.quarkus.keycloak.devservices.port=8180 %dev.quarkus.keycloak.devservices.service-name=quarkus-cryptomator-hub %dev.quarkus.keycloak.devservices.image-name=ghcr.io/cryptomator/keycloak:23.0.7 @@ -63,6 +76,10 @@ quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway %dev.quarkus.flyway.ignore-missing-migrations=true +%dev.quarkus.flyway.validate-migration-naming=true +# https://quarkus.io/guides/databases-dev-services +# https://stackoverflow.com/questions/44654216/correct-way-to-install-psql-without-full-postgres-on-macos +# psql -h localhost -p 54082 -U quarkus -d quarkus # log Hibernate SQL statements including values, for dev-purpose only %dev.quarkus.log.min-level=TRACE @@ -84,6 +101,8 @@ quarkus.http.header."Referrer-Policy".value=no-referrer quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains quarkus.http.header."X-Content-Type-Options".value=nosniff quarkus.http.header."X-Frame-Options".value=deny +# dev-ui needs very permissive xfo: +#%dev.quarkus.http.header."X-Frame-Options".value=sameorigin quarkus.http.header."X-Permitted-Cross-Domain-Policies".value=none quarkus.http.header."Cross-Origin-Embedder-Policy".value=credentialless quarkus.http.header."Cross-Origin-Opener-Policy".value=same-origin @@ -113,12 +132,12 @@ quarkus.http.filter.static.matches=/(favicon.ico|logo.svg) # Container Image Adjustments quarkus.container-image.registry=ghcr.io -quarkus.container-image.group=cryptomator -quarkus.container-image.name=hub +quarkus.container-image.group=shift7-ch +quarkus.container-image.name=cipherduck quarkus.container-image.tag=latest -quarkus.container-image.labels."org.opencontainers.image.title"=Cryptomator Hub +quarkus.container-image.labels."org.opencontainers.image.title"=Cipherduck Hub quarkus.container-image.labels."org.opencontainers.image.description"=Centralized Zero-Knowledge Key Management for using Cryptomator in Teams and Organizations -quarkus.container-image.labels."org.opencontainers.image.vendor"=Skymatic GmbH -quarkus.container-image.labels."org.opencontainers.image.url"=https://cryptomator.org/hub -quarkus.container-image.labels."org.opencontainers.image.source"=https://github.com/cryptomator/hub +quarkus.container-image.labels."org.opencontainers.image.vendor"=Shift7 GmbH +quarkus.container-image.labels."org.opencontainers.image.url"=https://github.com/shift7-ch/cipherduck-hub +quarkus.container-image.labels."org.opencontainers.image.source"=https://github.com/shift7-ch/cipherduck-hub quarkus.container-image.labels."org.opencontainers.image.licenses"=AGPL-3.0-or-later diff --git a/backend/src/main/resources/dev-realm.json b/backend/src/main/resources/dev-realm.json index d60eda306..890e566f6 100644 --- a/backend/src/main/resources/dev-realm.json +++ b/backend/src/main/resources/dev-realm.json @@ -39,12 +39,190 @@ "client": { "realm-management": [ "view-users", - "view-clients" + "view-clients", + "manage-users", + "manage-clients" ] } } } - ] + ], + "client": { + "realm-management": [ + { + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "attributes": {} + }, + { + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "attributes": {} + }, + { + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-clients", + "view-identity-providers", + "view-events", + "manage-clients", + "manage-identity-providers", + "create-client", + "view-users", + "query-users", + "view-realm", + "query-groups", + "manage-users", + "manage-events", + "view-authorization", + "manage-realm", + "query-realms", + "manage-authorization", + "query-clients", + "impersonation" + ] + } + }, + "clientRole": true, + "attributes": {} + }, + { + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "attributes": {} + }, + { + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "attributes": {} + } + ] + } }, "users": [ { @@ -166,6 +344,7 @@ "attributes": { "pkce.code.challenge.method": "S256" }, + "directAccessGrantsEnabled": false, "protocolMappers": [ { "name": "realm roles", @@ -180,18 +359,30 @@ } }, { - "name": "client roles", + "name": "aud", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", + "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { + "included.client.audience": "cryptomatorhub", + "id.token.claim": "false", "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true", - "usermodel.clientRoleMapping.clientId": "cryptomatorhub" + "userinfo.token.claim": "false" } } + ], + "defaultClientScopes": [ + "web-origins", + "phone", + "profile", + "email" + ], + "optionalClientScopes": [ + "acr", + "address", + "roles", + "offline_access", + "microprofile-jwt" ] }, { @@ -201,7 +392,8 @@ "name": "Cryptomator App", "enabled": true, "redirectUris": [ - "http://127.0.0.1/*" + "http://127.0.0.1/*", + "x-cipherduck-action:oauth" ], "webOrigins": [ "+" @@ -211,17 +403,130 @@ "protocol": "openid-connect", "attributes": { "pkce.code.challenge.method": "S256" - } + }, + "directAccessGrantsEnabled": false, + "protocolMappers": [ + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "aud", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "cryptomator", + "id.token.claim": "false", + "access.token.claim": "true", + "userinfo.token.claim": "false" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "phone", + "profile", + "email" + ], + "optionalClientScopes": [ + "acr", + "address", + "offline_access", + "microprofile-jwt", + "roles" + ] }, { - "clientId": "cryptomatorhub-cli", - "name": "Cryptomator Hub CLI", + "id": "367e3049-23ee-4714-a7ed-75e61d027d02", + "clientId": "cryptomatorvaults", + "name": "Cryptomator Vaults (Token Exchange)", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "secret": "top-secret", - "standardFlowEnabled": false, - "serviceAccountsEnabled": true, - "publicClient": false, - "enabled": true + "redirectUris": [], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "clientId": "realm-management", + "authorizationSettings": { + "allowRemoteResourceManagement": false, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "client.resource.367e3049-23ee-4714-a7ed-75e61d027d02", + "type": "Client", + "ownerManagedAccess": false, + "attributes": {}, + "_id": "67da8209-a6fb-438f-8ee7-f936b47aedf4", + "uris": [], + "scopes": [ + { + "name": "token-exchange" + } + ] + } + ], + "policies": [ + { + "name": "exchange", + "description": "", + "type": "client", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "clients": "[\"cryptomator\"]" + } + }, + { + "name": "token-exchange.permission.client.367e3049-23ee-4714-a7ed-75e61d027d02", + "type": "scope", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"client.resource.367e3049-23ee-4714-a7ed-75e61d027d02\"]", + "scopes": "[\"token-exchange\"]", + "applyPolicies": "[\"exchange\"]" + } + } + ], + "scopes": [ + { + "name": "token-exchange" + } + ], + "decisionStrategy": "UNANIMOUS" + } } ], "browserSecurityHeaders": { diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql new file mode 100644 index 000000000..6d794fd8e --- /dev/null +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V80__StorageProfile.sql @@ -0,0 +1,56 @@ +CREATE TABLE "storage_profile" +( + "id" UUID NOT NULL, + "name" VARCHAR, + + -- (3) client profile + -- (3a) client profile attributes + "protocol" VARCHAR NOT NULL, + "archived" bool NOT NULL, + + CONSTRAINT "STORAGE_PROFILE_PK" PRIMARY KEY ("id") +); + +CREATE TABLE "storage_profile_s3" +( + "id" UUID NOT NULL, + + -- (1) bucket creation, template upload and client profile + "scheme" VARCHAR, + "hostname" VARCHAR, + "port" INT4, + "withPathStyleAccessEnabled" + bool NOT NULL, + "storageClass" VARCHAR NOT NULL, + + + CONSTRAINT "STORAGE_PROFILE_S3_PK" PRIMARY KEY ("id"), + CONSTRAINT "STORAGE_PROFILE_S3_FK_STORAGE_PROFILE" FOREIGN KEY ("id") REFERENCES "storage_profile" ("id") ON DELETE CASCADE, + CONSTRAINT "STORAGE_PROFILE_S3_CHK_STORAGE_CLASS" CHECK ("storageClass" = 'STANDARD' OR "storageClass" = 'INTELLIGENT_TIERING' OR "storageClass" = 'STANDARD_IA' OR "storageClass" = 'ONEZONE_IA' OR "storageClass" = 'GLACIER' OR "storageClass" = 'GLACIER_IR' OR "storageClass" = 'DEEP_ARCHIVE') +); + +CREATE TABLE "storage_profile_s3_sts" +( + "id" UUID NOT NULL, + + -- (2) bucket creation only (i.e. STS-case) + "region" VARCHAR, + "regions" text[], + "bucketPrefix" VARCHAR NOT NULL, + "stsRoleArnClient" VARCHAR NOT NULL, + "stsRoleArnHub" VARCHAR NOT NULL, + "stsEndpoint" VARCHAR, + "bucketVersioning" bool NOT NULL, + "bucketAcceleration" + bool NOT NULL, + "bucketEncryption" VARCHAR NOT NULL, + + -- (3b) client profile custom properties + "stsRoleArn" VARCHAR NOT NULL, + "stsRoleArn2" VARCHAR, + "stsDurationSeconds" INT4, + + CONSTRAINT "STORAGE_PROFILE_S3_STS_PK" PRIMARY KEY ("id"), + CONSTRAINT "STORAGE_PROFILE_S3_STS_FK_STORAGE_PROFILE_S3" FOREIGN KEY ("id") REFERENCES "storage_profile_s3" ("id") ON DELETE CASCADE, + CONSTRAINT "STORAGE_PROFILE_S3_CHK_BUCKET_ENCRYPTION" CHECK ("bucketEncryption" = 'NONE' OR "bucketEncryption" = 'SSE_AES256' OR "bucketEncryption" = 'SSE_KMS_DEFAULT') +); \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index f71ff158c..25685e754 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,7 +5,7 @@ - Cryptomator Hub + Cipherduck
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5cc907a46..420cffe98 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,8 @@ "version": "1.4.0", "license": "AGPL-3.0-or-later", "dependencies": { + "@aws-sdk/client-s3": "^3.454.0", + "@aws-sdk/client-sts": "^3.421.0", "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", @@ -87,6 +89,886 @@ "node": ">=6.0.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", + "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", + "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.454.0.tgz", + "integrity": "sha512-vBx4iB1c4mEz+twMnl6angC1/IpnmXaT8L6Kl9uNiurFsb6N4tCyrJ24kECdsOSTM7ePXvE1fKst8zAh6MW5ZA==", + "dependencies": { + "@aws-crypto/sha1-browser": "3.0.0", + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.454.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-bucket-endpoint": "3.451.0", + "@aws-sdk/middleware-expect-continue": "3.451.0", + "@aws-sdk/middleware-flexible-checksums": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-location-constraint": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-sdk-s3": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-ssec": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/signature-v4-multi-region": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@aws-sdk/xml-builder": "3.310.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/eventstream-serde-browser": "^2.0.13", + "@smithy/eventstream-serde-config-resolver": "^2.0.13", + "@smithy/eventstream-serde-node": "^2.0.13", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-blob-browser": "^2.0.14", + "@smithy/hash-node": "^2.0.15", + "@smithy/hash-stream-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/md5-js": "^2.0.15", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-stream": "^2.0.20", + "@smithy/util-utf8": "^2.0.2", + "@smithy/util-waiter": "^2.0.13", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz", + "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.454.0.tgz", + "integrity": "sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-sdk-sts": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/core": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz", + "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==", + "dependencies": { + "@smithy/smithy-client": "^2.1.15", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz", + "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz", + "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz", + "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-ini": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz", + "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz", + "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==", + "dependencies": { + "@aws-sdk/client-sso": "3.451.0", + "@aws-sdk/token-providers": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz", + "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.451.0.tgz", + "integrity": "sha512-KWyZ1JGnYz2QbHuJtYTP1BVnMOfVopR8rP8dTinVb/JR5HfAYz4imICJlJUbOYRjN7wpA3PrRI8dNRjrSBjWJg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-arn-parser": "3.310.0", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.451.0.tgz", + "integrity": "sha512-vwG8o2Uk6biLDlOZnqXemsO4dS2HvrprUdxyouwu6hlzLFskg8nL122butn19JqXJKgcVLuSSLzT+xwqBWy2Rg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.451.0.tgz", + "integrity": "sha512-eOkpcC2zgAvqs1w7Yp5nsk9LBIj6qLU5kaZuZEBOiFbNKIrTnPo6dQuhgvDcKHD6Y5W/cUjSBiFMs/ROb5aoug==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@aws-crypto/crc32c": "3.0.0", + "@aws-sdk/types": "3.451.0", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz", + "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.451.0.tgz", + "integrity": "sha512-R4U2G7mybP0BMiQBJWTcB47g49F4PSXTiCsvMDp5WOEhpWvGQuO1ZIhTxCl5s5lgTSne063Os8W6KSdK2yG2TQ==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz", + "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz", + "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.451.0.tgz", + "integrity": "sha512-XF4Cw8HrYUwGLKOqKtWs6ss1WXoxvQUcgGLACGSqn9a0p51446NiS5671x7qJUsfBuygdKlIKcOc8pPr9a+5Ow==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-arn-parser": "3.310.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz", + "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==", + "dependencies": { + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz", + "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.451.0.tgz", + "integrity": "sha512-hDkeBUiRsvuDbvsPha0/uJHE680WDzjAOoE6ZnLBoWsw7ry+Bw1ULMj0sCmpBVrQ7Gpivi/6zbezhClVmt3ITw==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz", + "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz", + "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.451.0.tgz", + "integrity": "sha512-qQKY7/txeNUTLyRL3WxUWEwaZ5sf76EIZgu9kLaR96cAYSxwQi/qQB3ijbfD6u7sJIA8aROMxeYK0VmRsQg0CA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz", + "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/types": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz", + "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", + "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz", + "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/util-endpoints": "^1.0.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", + "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz", + "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz", + "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", + "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -1463,228 +2345,1084 @@ "run-parallel": "^1.1.9" }, "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@smithy/abort-controller": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", + "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/abort-controller/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz", + "integrity": "sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.1.tgz", + "integrity": "sha512-N2oCZRglhWKm7iMBu7S6wDzXirjAofi7tAd26cxmgibRYOBS4D3hGfmkwCpHdASZzwZDD8rluh0Rcqw1JeZDRw==", + "dependencies": { + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/chunked-blob-reader/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/config-resolver": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz", + "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/config-resolver/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz", + "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-codec": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz", + "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.13.tgz", + "integrity": "sha512-OJ/2g/VxkzA+mYZxV102oX3CsiE+igTSmqq/ir3oEVG2kSIdRC00ryttj/lmL14W06ExNi0ysmfLxQkL8XrAZQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.13.tgz", + "integrity": "sha512-2BI1CbnYuEvAYoWSeWJtPNygbIKiWeSLxCmDLnyM6wQV32Of7VptiQlaFXPxXp4zqn/rs3ocZ/T29rxE4s4Gsg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.13.tgz", + "integrity": "sha512-7NbFwPafb924elFxCBDvm48jy/DeSrpFbFQN0uN2ThuY5HrEeubikS0t7WMva4Z4EnRoivpbuT0scb9vUIJKoA==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.13.tgz", + "integrity": "sha512-j0yFd5UfftM+ia9dxLRbheJDCkCZBHpcEzCsPO8BxVOTbdcX/auVJCv6ov/yvpCKsf4Hv3mOqi0Is1YogM2g3Q==", + "dependencies": { + "@smithy/eventstream-codec": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", + "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", + "dependencies": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.14.tgz", + "integrity": "sha512-yWdghyPJIEqLYsaE7YVgd3YhM7jN4Pv6eJQvTomnMsz5K2qRBlpjUx3T9fKlElp1qdeQ7DNc3sAat4i9CUBO7Q==", + "dependencies": { + "@smithy/chunked-blob-reader": "^2.0.0", + "@smithy/chunked-blob-reader-native": "^2.0.1", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/hash-blob-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/hash-node": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz", + "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/hash-stream-node": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.0.15.tgz", + "integrity": "sha512-ZZ6kC/pHt5Dc2goXIIyC8uA7A4GUMSzdCynAabnZ3CSSaV6ctP8mlvVkqjPph0O3XzHlx/80gdLrNqi1GDPUsA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/invalid-dependency": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz", + "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/invalid-dependency/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/is-array-buffer/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/md5-js": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.15.tgz", + "integrity": "sha512-pAZaokib56XvhU0t/R9vAcr3L3bMhIakhF25X7EMSQ7LAURiLfce/tgON8I3x/dIbnZUyeRi8f2cx2azu6ATew==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-content-length": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz", + "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==", + "dependencies": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz", + "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==", + "dependencies": { + "@smithy/middleware-serde": "^2.0.13", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-retry": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz", + "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/protocol-http": "^3.0.9", + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-retry": "^2.0.6", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-serde": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz", + "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-serde/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/middleware-stack": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", + "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-stack/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-config-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/node-http-handler": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", + "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", + "dependencies": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/property-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/protocol-http": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", + "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/querystring-builder": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", + "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-builder/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/querystring-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz", + "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-parser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/service-error-classification": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz", + "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==", + "dependencies": { + "@smithy/types": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/signature-v4": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz", + "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==", + "dependencies": { + "@smithy/eventstream-codec": "^2.0.13", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/smithy-client": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", + "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", + "dependencies": { + "@smithy/middleware-stack": "^2.0.7", + "@smithy/types": "^2.5.0", + "@smithy/util-stream": "^2.0.20", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/smithy-client/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/url-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz", + "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==", + "dependencies": { + "@smithy/querystring-parser": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/url-parser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", + "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-body-length-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", + "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-body-length-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-buffer-from/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@smithy/util-config-provider/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz", + "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">= 8" + "node": ">= 10.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz", + "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@smithy/config-resolver": "^2.0.18", + "@smithy/credential-provider-imds": "^2.1.1", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">= 8" + "node": ">= 10.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, + "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-endpoints": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz", + "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=14" + "node": ">= 14.0.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "dev": true, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@smithy/util-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", + "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@smithy/util-middleware/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@smithy/util-retry": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz", + "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==", + "dependencies": { + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-retry/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-stream": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", + "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", + "dependencies": { + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-stream/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-uri-escape/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-utf8/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-waiter": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.13.tgz", + "integrity": "sha512-YovIQatiuM7giEsRFotqJa2i3EbU2EE3PgtpXgtLgpx5rXiZMAwPxXYDFVFhuO0lbqvc/Zx4n+ZIisXOHPSqyg==", + "dependencies": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-waiter/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@tailwindcss/forms": { "version": "0.5.7", @@ -2406,6 +4144,11 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3234,6 +4977,27 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -5635,6 +7399,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -5938,6 +7707,11 @@ "node": ">=0.3.1" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6064,7 +7838,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } diff --git a/frontend/package.json b/frontend/package.json index 6810a8230..a954d1af9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,6 +48,8 @@ "vue-tsc": "^1.8.27" }, "dependencies": { + "@aws-sdk/client-s3": "^3.454.0", + "@aws-sdk/client-sts": "^3.421.0", "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index 0196012cc..9847f0958 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -195,6 +195,71 @@ export type VersionDto = { keycloakVersion: string; } +// / start cipherduck extension +export type StorageDto = { + vaultId: string; + storageConfigId: string; + vaultConfigToken: string; + rootDirHash: string; + awsAccessKey: string; + awsSecretKey: string; + sessionToken: string; + region: string; +} + +export type ConfigDto = { + keycloakUrl: string; + keycloakRealm: string; + keycloakClientIdHub: string; + keycloakClientIdCryptomator: string; + keycloakAuthEndpoint: string; + keycloakTokenEndpoint: string; + serverTime: string; + apiLevel: number; + uuid: string; +} + +export type StorageProfileDto = { + id: string; + name: string; + bucketPrefix: string; + stsRoleArnClient: string; + stsRoleArnHub: string; + stsEndpoint: string; + region: string; + regions: string[]; + withPathStyleAccessEnabled: boolean; + scheme: string; + hostname: string; + port: number; + protocol: string; + oauthClientId: string; + oauthTokenUrl: string; + oauthAuthorizationUrl: string; + stsRoleArn: string; + stsRoleArn2: string; + stsDurationSeconds: number; + oAuthTokenExchangeAudience: number; +} + +export type AutomaticAccessGrant = { + enabled: boolean, + maxWotDepth: number +} + +export type VaultJWEBackendDto = { + provider: string; + + defaultPath: string; + nickname: string; + + region: string; + + username?: string; + password?: string; +} +// \ end cipherduck extension + /* Services */ export interface VaultIdHeader extends JWTHeader { @@ -361,6 +426,27 @@ class VersionService { } } +// / start cipherduck extension +class StorageService { + public async put(vaultId: string, dto: StorageDto): Promise { + return axiosAuth.put(`/storage/${vaultId}/`, dto); + } +} +class StorageProfileService { + public async get(archived?: boolean): Promise { + return axiosAuth.get(`/storageprofile?archived=${archived}`) + .then(response => response.data); + } +} +export const axiosUnAuth = AxiosStatic.create(axiosBaseCfg) +class ConfigService { + public async config(): Promise { + return axiosUnAuth.get('/config') + .then(response => response.data); + } +} +// \ end cipherduck extension + /** * Note: Each service can thrown an {@link UnauthorizedError} when the access token is expired! */ @@ -371,6 +457,12 @@ const services = { devices: new DeviceService(), billing: new BillingService(), version: new VersionService() + + // / start cipherduck extension + ,storage: new StorageService() + ,storageprofiles: new StorageProfileService() + ,config: new ConfigService() + // \ end cipherduck extension }; function convertExpectedToBackendError(status: number): BackendError { diff --git a/frontend/src/common/crypto.ts b/frontend/src/common/crypto.ts index 9895b0b08..eb54c6e59 100644 --- a/frontend/src/common/crypto.ts +++ b/frontend/src/common/crypto.ts @@ -2,6 +2,11 @@ import * as miscreant from 'miscreant'; import { base16, base32, base64, base64url } from 'rfc4648'; import { JWEBuilder, JWEParser } from './jwe'; import { CRC32, DB, wordEncoder } from './util'; + +// / start cipherduck extension +import { VaultJWEBackendDto, AutomaticAccessGrant } from './backend'; +// \ end cipherduck extension + export class UnwrapKeyError extends Error { readonly actualError: any; @@ -29,8 +34,14 @@ export interface VaultConfigHeaderHub { devicesResourceUrl: string } +// TODO review @overheadhunter should we distinguish between JWEPayload (only key) and VaultJWEPayload (all three) interface JWEPayload { key: string + + // / start cipherduck extension + ,automaticAccessGrant?: AutomaticAccessGrant + ,backend?: VaultJWEBackendDto + // \ end cipherduck extension } const GCM_NONCE_LEN = 12; @@ -48,8 +59,25 @@ export class VaultKeys { readonly masterKey: CryptoKey; - protected constructor(masterkey: CryptoKey) { + // / start cipherduck extension + automaticAccessGrant?: AutomaticAccessGrant; + storage?: VaultJWEBackendDto; + // \ end cipherduck extension + + + + protected constructor(masterkey: CryptoKey + // / start cipherduck extension + ,automaticAccessGrant?: AutomaticAccessGrant + ,storage?: VaultJWEBackendDto + // \ end cipherduck extension + ) { this.masterKey = masterkey; + + // / start cipherduck extension + this.automaticAccessGrant = automaticAccessGrant; + this.storage = storage; + // \ end cipherduck extension } /** @@ -76,8 +104,18 @@ export class VaultKeys { try { const payload: JWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(userPrivateKey); rawKey = base64.parse(payload.key); + const masterkey = crypto.subtle.importKey('raw', rawKey, VaultKeys.MASTERKEY_KEY_DESIGNATION, true, ['sign']); - return new VaultKeys(await masterkey); + // / start cipherduck extension + const automaticAccessGrant = payload.automaticAccessGrant; + const backend = payload.backend; + // \ end cipherduck extension + return new VaultKeys(await masterkey + // / start cipherduck extension + ,automaticAccessGrant + ,backend + // \ end cipherduck extension + ); } finally { rawKey.fill(0x00); } @@ -226,6 +264,13 @@ export class VaultKeys { const payload: JWEPayload = { key: base64.stringify(rawkey) }; + + // / start cipherduck extension + if (this.storage != undefined){ + payload['backend'] = this.storage; + } + // \ end cipherduck extension + return JWEBuilder.ecdhEs(publicKey).encrypt(payload); } finally { rawkey.fill(0x00); diff --git a/frontend/src/common/settings.ts b/frontend/src/common/settings.ts new file mode 100644 index 000000000..2481e2e7b --- /dev/null +++ b/frontend/src/common/settings.ts @@ -0,0 +1,2 @@ +// TODO review is there a standard way for injecting developer properties? +export const showVaultIDs = true; \ No newline at end of file diff --git a/frontend/src/common/vaultconfig.ts b/frontend/src/common/vaultconfig.ts index ba6b380f5..3b8daa7cb 100644 --- a/frontend/src/common/vaultconfig.ts +++ b/frontend/src/common/vaultconfig.ts @@ -4,7 +4,9 @@ import { VaultConfigHeaderHub, VaultConfigPayload, VaultKeys } from '../common/c export class VaultConfig { readonly vaultConfigToken: string; - private readonly rootDirHash: string; + // / cipherduck modification + readonly rootDirHash: string; + // \ cipherduck modification private constructor(vaultConfigToken: string, rootDirHash: string) { this.vaultConfigToken = vaultConfigToken; diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue index 02cca908b..df02f9e2d 100644 --- a/frontend/src/components/CreateVault.vue +++ b/frontend/src/components/CreateVault.vue @@ -64,6 +64,146 @@ ({{ t('common.optional') }}) + + + +
+ + + + {{ selectedBackend ? selectedBackend.name : '' }} + + + + + +
+ + +
  • + {{ backend.name }} + + +
  • +
    +
    +
    +
    +
    +
    +
    +
    + + + + {{ selectedRegion }} + + + + + +
    + + +
  • + {{ region }} + + +
  • +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + @@ -73,6 +213,7 @@

    {{ t('createVault.error.formValidationFailed') }}

    {{ t('common.unexpectedError', [onCreateError.message]) }}

    +
    {{ onCreateError.codehint }}
    -

    {{ t('createVault.error.downloadTemplateFailed', [onDownloadTemplateError.message]) }}

    +

    {{ t('CreateVaultS3.error.openBookmarkFailed', [onOpenBookmarkError.message]) }}

    + +
    +

    {{ t('CreateVaultS3.error.uploadTemplateFailed') }}{{ onUploadTemplateError.message == null ? '' : ': ' + onUploadTemplateError.message }}

    +
    {{ t('createVault.success.return') }} @@ -179,8 +327,10 @@ diff --git a/frontend/src/components/NavigationBar.vue b/frontend/src/components/NavigationBar.vue index 8edd89672..ebf9b103a 100644 --- a/frontend/src/components/NavigationBar.vue +++ b/frontend/src/components/NavigationBar.vue @@ -13,7 +13,9 @@
    Logo - CRYPTOMATOR HUB + + CIPHERDUCK +
    + +

    {{ vault.id }}

    + +

    {{ t('vaultDetails.description.header') }}

    @@ -130,9 +134,13 @@ + + + @@ -205,6 +213,10 @@ import RecoverVaultDialog from './RecoverVaultDialog.vue'; import RecoveryKeyDialog from './RecoveryKeyDialog.vue'; import SearchInputGroup from './SearchInputGroup.vue'; +// / start cipherduck extension +import { showVaultIDs } from '../common/settings'; +// \ end cipherduck extension + const { t, d } = useI18n({ useScope: 'global' }); const props = defineProps<{ @@ -398,10 +410,12 @@ function showEditVaultMetadataDialog() { nextTick(() => editVaultMetadataDialog.value?.show()); } -function showDownloadVaultTemplateDialog() { - downloadingVaultTemplate.value = true; - nextTick(() => downloadVaultTemplateDialog.value?.show()); -} +// / start cipherduck modification +//function showDownloadVaultTemplateDialog() { +// downloadingVaultTemplate.value = true; +// nextTick(() => downloadVaultTemplateDialog.value?.show()); +//} +// \ end cipherduck modification --> function showRecoveryKeyDialog() { showingRecoveryKey.value = true; diff --git a/frontend/src/components/VaultList.vue b/frontend/src/components/VaultList.vue index 3b0e892ca..171f764e3 100644 --- a/frontend/src/components/VaultList.vue +++ b/frontend/src/components/VaultList.vue @@ -69,6 +69,13 @@ + + + +
    @@ -83,6 +90,9 @@
    {{ t('vaultList.badge.archived') }}

    {{ vault.description }}

    + +

    {{ vault.id }}

    +