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 }}

    +