diff --git a/.dockerignore b/.dockerignore index ecd919cd..df875b66 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,4 +31,5 @@ dist migrations/ config/ build.ts -tsconfig.json \ No newline at end of file +tsconfig.json +migrations/ diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 0e4d9bc6..2ebb4663 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline +name: Public Pipeline # CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries. # Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events. @@ -440,6 +440,10 @@ jobs: issuer="https://token.actions.githubusercontent.com" id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs) + # Track failures + FAILED_TAGS=() + SUCCESSFUL_TAGS=() + # Determine if this is an RC release IS_RC="false" if [[ "$TAG" == *"-rc."* ]]; then @@ -471,94 +475,123 @@ jobs: for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}" + TAG_FAILED=false - DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')" - REF="${BASE_IMAGE}@${DIGEST}" - echo "Resolved digest: ${REF}" + # Wrap the entire tag processing in error handling + ( + set -e + DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')" + REF="${BASE_IMAGE}@${DIGEST}" + echo "Resolved digest: ${REF}" - echo "==> cosign sign (keyless) --recursive ${REF}" - cosign sign --recursive "${REF}" + echo "==> cosign sign (keyless) --recursive ${REF}" + cosign sign --recursive "${REF}" - echo "==> cosign sign (key) --recursive ${REF}" - cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" + echo "==> cosign sign (key) --recursive ${REF}" + cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" - # Retry wrapper for verification to handle registry propagation delays - retry_verify() { - local cmd="$1" - local attempts=6 - local delay=5 - local i=1 - until eval "$cmd"; do - if [ $i -ge $attempts ]; then - echo "Verification failed after $attempts attempts" - return 1 - fi - echo "Verification not yet available. Retry $i/$attempts after ${delay}s..." - sleep $delay - i=$((i+1)) - delay=$((delay*2)) - # Cap the delay to avoid very long waits - if [ $delay -gt 60 ]; then delay=60; fi - done - return 0 - } - - echo "==> cosign verify (public key) ${REF}" - if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then - VERIFIED_INDEX=true - else - VERIFIED_INDEX=false - fi - - echo "==> cosign verify (keyless policy) ${REF}" - if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then - VERIFIED_INDEX_KEYLESS=true - else - VERIFIED_INDEX_KEYLESS=false - fi - - # If index verification fails, attempt to verify child platform manifests - if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then - echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}" - CHILD_VERIFIED=false - - for ARCH in arm64 amd64; do - CHILD_TAG="${IMAGE_TAG}-${ARCH}" - echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}" - CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)" - if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then - CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}" - echo "==> cosign verify (public key) child ${CHILD_REF}" - if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then - CHILD_VERIFIED=true - echo "Public key verification succeeded for child ${CHILD_REF}" - else - echo "Public key verification failed for child ${CHILD_REF}" + # Retry wrapper for verification to handle registry propagation delays + retry_verify() { + local cmd="$1" + local attempts=6 + local delay=5 + local i=1 + until eval "$cmd"; do + if [ $i -ge $attempts ]; then + echo "Verification failed after $attempts attempts" + return 1 fi + echo "Verification not yet available. Retry $i/$attempts after ${delay}s..." + sleep $delay + i=$((i+1)) + delay=$((delay*2)) + # Cap the delay to avoid very long waits + if [ $delay -gt 60 ]; then delay=60; fi + done + return 0 + } - echo "==> cosign verify (keyless policy) child ${CHILD_REF}" - if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then - CHILD_VERIFIED=true - echo "Keyless verification succeeded for child ${CHILD_REF}" - else - echo "Keyless verification failed for child ${CHILD_REF}" - fi - else - echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping" - fi - done - - if [ "${CHILD_VERIFIED}" != "true" ]; then - echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}" - exit 10 + echo "==> cosign verify (public key) ${REF}" + if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then + VERIFIED_INDEX=true + else + VERIFIED_INDEX=false fi - fi - echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}" + echo "==> cosign verify (keyless policy) ${REF}" + if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then + VERIFIED_INDEX_KEYLESS=true + else + VERIFIED_INDEX_KEYLESS=false + fi + + # If index verification fails, attempt to verify child platform manifests + if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then + echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}" + CHILD_VERIFIED=false + + for ARCH in arm64 amd64; do + CHILD_TAG="${IMAGE_TAG}-${ARCH}" + echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}" + CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)" + if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then + CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}" + echo "==> cosign verify (public key) child ${CHILD_REF}" + if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then + CHILD_VERIFIED=true + echo "Public key verification succeeded for child ${CHILD_REF}" + else + echo "Public key verification failed for child ${CHILD_REF}" + fi + + echo "==> cosign verify (keyless policy) child ${CHILD_REF}" + if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then + CHILD_VERIFIED=true + echo "Keyless verification succeeded for child ${CHILD_REF}" + else + echo "Keyless verification failed for child ${CHILD_REF}" + fi + else + echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping" + fi + done + + if [ "${CHILD_VERIFIED}" != "true" ]; then + echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}" + exit 1 + fi + fi + ) || TAG_FAILED=true + + if [ "$TAG_FAILED" = "true" ]; then + echo "⚠️ WARNING: Failed to sign/verify ${BASE_IMAGE}:${IMAGE_TAG}" + FAILED_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}") + else + echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}" + SUCCESSFUL_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}") + fi done done - echo "All images signed and verified successfully!" + # Report summary + echo "" + echo "==========================================" + echo "Sign and Verify Summary" + echo "==========================================" + echo "Successful: ${#SUCCESSFUL_TAGS[@]}" + echo "Failed: ${#FAILED_TAGS[@]}" + echo "" + + if [ ${#FAILED_TAGS[@]} -gt 0 ]; then + echo "Failed tags:" + for tag in "${FAILED_TAGS[@]}"; do + echo " - $tag" + done + echo "" + echo "⚠️ WARNING: Some tags failed to sign/verify, but continuing anyway" + else + echo "✓ All images signed and verified successfully!" + fi shell: bash post-run: diff --git a/.github/workflows/cicd.yml.backup b/.github/workflows/cicd.yml.backup deleted file mode 100644 index 09e406ad..00000000 --- a/.github/workflows/cicd.yml.backup +++ /dev/null @@ -1,426 +0,0 @@ -name: CI/CD Pipeline - -# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries. -# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events. - -permissions: - contents: read - packages: write # for GHCR push - id-token: write # for Cosign Keyless (OIDC) Signing - -# Required secrets: -# - DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN: push to Docker Hub -# - GITHUB_TOKEN: used for GHCR login and OIDC keyless signing -# - COSIGN_PRIVATE_KEY / COSIGN_PASSWORD / COSIGN_PUBLIC_KEY: for key-based signing - -on: - push: - tags: - - "[0-9]+.[0-9]+.[0-9]+" - - "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - pre-run: - runs-on: ubuntu-latest - permissions: write-all - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} - role-duration-seconds: 3600 - aws-region: ${{ secrets.AWS_REGION }} - - - name: Verify AWS identity - run: aws sts get-caller-identity - - - name: Start EC2 instances - run: | - aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }} - aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }} - echo "EC2 instances started" - - - release-arm: - name: Build and Release (ARM64) - runs-on: [self-hosted, linux, arm64, us-east-1] - needs: [pre-run] - if: >- - ${{ - needs.pre-run.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Monitor storage space - run: | - THRESHOLD=75 - USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g') - echo "Used space: $USED_SPACE%" - if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then - echo "Used space is below the threshold of 75% free. Running Docker system prune." - echo y | docker system prune -a - else - echo "Storage space is above the threshold. No action needed." - fi - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Build and push Docker images (Docker Hub - ARM64) - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make build-rc-arm tag=$TAG - else - make build-release-arm tag=$TAG - fi - echo "Built & pushed ARM64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}" - shell: bash - - release-amd: - name: Build and Release (AMD64) - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [pre-run] - if: >- - ${{ - needs.pre-run.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Monitor storage space - run: | - THRESHOLD=75 - USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g') - echo "Used space: $USED_SPACE%" - if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then - echo "Used space is below the threshold of 75% free. Running Docker system prune." - echo y | docker system prune -a - else - echo "Storage space is above the threshold. No action needed." - fi - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Build and push Docker images (Docker Hub - AMD64) - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make build-rc-amd tag=$TAG - else - make build-release-amd tag=$TAG - fi - echo "Built & pushed AMD64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}" - shell: bash - - create-manifest: - name: Create Multi-Arch Manifests - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [release-arm, release-amd] - if: >- - ${{ - needs.release-arm.result == 'success' && - needs.release-amd.result == 'success' - }} - timeout-minutes: 30 - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Check if release candidate - id: check-rc - run: | - TAG=${{ env.TAG }} - if [[ "$TAG" == *"-rc."* ]]; then - echo "IS_RC=true" >> $GITHUB_ENV - else - echo "IS_RC=false" >> $GITHUB_ENV - fi - shell: bash - - - name: Create multi-arch manifests - run: | - TAG=${{ env.TAG }} - if [ "$IS_RC" = "true" ]; then - make create-manifests-rc tag=$TAG - else - make create-manifests tag=$TAG - fi - echo "Created multi-arch manifests for tag: ${TAG}" - shell: bash - - sign-and-package: - name: Sign and Package - runs-on: [self-hosted, linux, x64, us-east-1] - needs: [release-arm, release-amd, create-manifest] - if: >- - ${{ - needs.release-arm.result == 'success' && - needs.release-amd.result == 'success' && - needs.create-manifest.result == 'success' - }} - # Job-level timeout to avoid runaway or stuck runs - timeout-minutes: 120 - env: - # Target images - DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} - GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} - - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Extract tag name - id: get-tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - shell: bash - - - name: Install Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - with: - go-version: 1.24 - - - name: Update version in package.json - run: | - TAG=${{ env.TAG }} - sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts - cat server/lib/consts.ts - shell: bash - - - name: Pull latest Gerbil version - id: get-gerbil-tag - run: | - LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') - echo "LATEST_GERBIL_TAG=$LATEST_TAG" >> $GITHUB_ENV - shell: bash - - - name: Pull latest Badger version - id: get-badger-tag - run: | - LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') - echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV - shell: bash - - - name: Update install/main.go - run: | - PANGOLIN_VERSION=${{ env.TAG }} - GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }} - BADGER_VERSION=${{ env.LATEST_BADGER_TAG }} - sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go - sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go - sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go - echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION" - cat install/main.go - shell: bash - - - name: Build installer - working-directory: install - run: | - make go-build-release - - - name: Upload artifacts from /install/bin - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: install-bin - path: install/bin/ - - - name: Install skopeo + jq - # skopeo: copy/inspect images between registries - # jq: JSON parsing tool used to extract digest values - run: | - sudo apt-get update -y - sudo apt-get install -y skopeo jq - skopeo --version - shell: bash - - - name: Login to GHCR - env: - REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json - run: | - mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")" - skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}" - shell: bash - - - name: Copy tag from Docker Hub to GHCR - # Mirror the already-built image (all architectures) to GHCR so we can sign it - # Wait a bit for both architectures to be available in Docker Hub manifest - env: - REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json - run: | - set -euo pipefail - TAG=${{ env.TAG }} - echo "Waiting for multi-arch manifest to be ready..." - sleep 30 - echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}" - skopeo copy --all --retry-times 3 \ - docker://$DOCKERHUB_IMAGE:$TAG \ - docker://$GHCR_IMAGE:$TAG - shell: bash - - - name: Login to GitHub Container Registry (for cosign) - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install cosign - # cosign is used to sign and verify container images (key and keyless) - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - - name: Dual-sign and verify (GHCR & Docker Hub) - # Sign each image by digest using keyless (OIDC) and key-based signing, - # then verify both the public key signature and the keyless OIDC signature. - env: - TAG: ${{ env.TAG }} - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} - COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} - COSIGN_YES: "true" - run: | - set -euo pipefail - - issuer="https://token.actions.githubusercontent.com" - id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs) - - for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do - echo "Processing ${IMAGE}:${TAG}" - - DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')" - REF="${IMAGE}@${DIGEST}" - echo "Resolved digest: ${REF}" - - echo "==> cosign sign (keyless) --recursive ${REF}" - cosign sign --recursive "${REF}" - - echo "==> cosign sign (key) --recursive ${REF}" - cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}" - - echo "==> cosign verify (public key) ${REF}" - cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text - - echo "==> cosign verify (keyless policy) ${REF}" - cosign verify \ - --certificate-oidc-issuer "${issuer}" \ - --certificate-identity-regexp "${id_regex}" \ - "${REF}" -o text - done - shell: bash - - post-run: - needs: [pre-run, release-arm, release-amd, create-manifest, sign-and-package] - if: >- - ${{ - always() && - needs.pre-run.result == 'success' && - (needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure') && - (needs.release-amd.result == 'success' || needs.release-amd.result == 'skipped' || needs.release-amd.result == 'failure') && - (needs.create-manifest.result == 'success' || needs.create-manifest.result == 'skipped' || needs.create-manifest.result == 'failure') && - (needs.sign-and-package.result == 'success' || needs.sign-and-package.result == 'skipped' || needs.sign-and-package.result == 'failure') - }} - runs-on: ubuntu-latest - permissions: write-all - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} - role-duration-seconds: 3600 - aws-region: ${{ secrets.AWS_REGION }} - - - name: Verify AWS identity - run: aws sts get-caller-identity - - - name: Stop EC2 instances - run: | - aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }} - aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }} - echo "EC2 instances stopped" diff --git a/.github/workflows/saas.yml b/.github/workflows/saas.yml index 4ceabfea..5db7aa2f 100644 --- a/.github/workflows/saas.yml +++ b/.github/workflows/saas.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline +name: SAAS Pipeline # CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries. # Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events. diff --git a/messages/bg-BG.json b/messages/bg-BG.json index a2568ee4..3f48085a 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Увеличаване на броя на сайтовете", "idpManage": "Управление на доставчици на идентичност", "idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата", + "idpGlobalModeBanner": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да активирате IdPs за всяка организация, редактирайте конфигурацията на сървъра и задайте режима на IdP към org. Вижте документацията. Ако желаете да продължите да използвате глобалните IdPs и да премахнете това от настройките на организацията, изрично задайте режима на global в конфигурацията.", + "idpGlobalModeBannerUpgradeRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да използвате доставчици на идентичност за всяка организация, трябва да надстроите до изданието Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в администраторския панел. За да използвате доставчици на идентичност за всяка организация, е необходим лиценз за изданието Enterprise.", "idpDeletedDescription": "Доставчик на идентичност успешно изтрит", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Край на следващата година", "actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация", "accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация", - "licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.", - "ossEnterpriseEditionRequired": "Enterprise Edition се изисква за използване на тази функция.", + "licenseRequiredToUse": "Изисква се лиценз за Enterprise Edition, за да използвате тази функция. Тази функция е също достъпна в Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Необходимо е изданието Enterprise, за да използвате тази функция. Тази функция е също достъпна в Pangolin Cloud.", "certResolver": "Решавач на сертификати", "certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.", "selectCertResolver": "Изберете решавач на сертификати", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 0b29fe47..c35488cd 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Zvýšit počet stránek", "idpManage": "Spravovat poskytovatele identity", "idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému", + "idpGlobalModeBanner": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v admin panelu. Chcete-li povolit IdP pro každou organizaci, upravte konfiguraci serveru a nastavte IdP režim na org. Viz dokumentace. Pokud chcete pokračovat v používání globálních IdP a zmizet z nastavení organizace, explicitně nastavte režim na globální v konfiguraci.", + "idpGlobalModeBannerUpgradeRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Spravujte globální IdP v admin panelu. Chcete-li použít poskytovatele identity pro každou organizaci, musíte přejít na Enterprise vydání.", + "idpGlobalModeBannerLicenseRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v admin panelu. Chcete-li použít poskytovatele identity pro každou organizaci, je vyžadována Enterprise licence.", "idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Konec následujícího roku", "actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci", "accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci", - "licenseRequiredToUse": "Pro použití této funkce je vyžadována licence pro podnikání.", - "ossEnterpriseEditionRequired": "Enterprise Edition je vyžadována pro použití této funkce.", + "licenseRequiredToUse": "Pro použití této funkce je vyžadována licence Enterprise Edition . Tato funkce je také dostupná v Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition je vyžadována pro použití této funkce. Tato funkce je také k dispozici v Pangolin Cloud.", "certResolver": "Oddělovač certifikátů", "certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.", "selectCertResolver": "Vyberte řešič certifikátů", diff --git a/messages/de-DE.json b/messages/de-DE.json index b1b59e65..a38e9ac4 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Anzahl der Standorte erhöhen", "idpManage": "Identitätsanbieter verwalten", "idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten", + "idpGlobalModeBanner": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt über alle Organisationen). Verwalten Sie globale IdPs im Admin-Panel. Um IdPs pro Organisation zu aktivieren, bearbeiten Sie die Server-Konfiguration und setzen Sie den IdP-Modus auf org. Siehe Dokumentation. Wenn Sie weiterhin globale IdPs verwenden und diese in den Organisationseinstellungen verschwinden lassen wollen, setzen Sie den Modus explizit auf global in der Konfiguration.", + "idpGlobalModeBannerUpgradeRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im Admin-Panelverwalten. Um Identitätsanbieter pro Organisation nutzen zu können, müssen Sie zur Enterprise Edition upgraden.", + "idpGlobalModeBannerLicenseRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im Admin-Panelverwalten. Um Identitätsanbieter pro Organisation zu verwenden, ist eine Enterprise-Lizenz erforderlich.", "idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Ende des folgenden Jahres", "actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen", "accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen", - "licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise-Lizenz erforderlich.", - "ossEnterpriseEditionRequired": "Die Enterprise Edition wird benötigt, um diese Funktion nutzen zu können.", + "licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise Edition Lizenz erforderlich. Diese Funktion ist auch in der Pangolin Cloud verfügbar.", + "ossEnterpriseEditionRequired": "Um diese Funktion nutzen zu können, ist die Enterprise Edition erforderlich. Diese Funktion ist auch in der Pangolin Cloud verfügbar.", "certResolver": "Zertifikatsauflöser", "certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.", "selectCertResolver": "Zertifikatsauflöser auswählen", diff --git a/messages/en-US.json b/messages/en-US.json index 685c37cd..aa9cb2b0 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Increase site count", "idpManage": "Manage Identity Providers", "idpManageDescription": "View and manage identity providers in the system", + "idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To enable IdPs per organization, edit the server config and set IdP mode to org. See the docs. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, you must upgrade to the Enterprise edition.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the admin panel. To use identity providers per organization, an Enterprise license is required.", "idpDeletedDescription": "Identity provider deleted successfully", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "End of following year", "actionLogsDescription": "View a history of actions performed in this organization", "accessLogsDescription": "View access auth requests for resources in this organization", - "licenseRequiredToUse": "An Enterprise license is required to use this feature.", - "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature.", + "licenseRequiredToUse": "An Enterprise Edition license is required to use this feature. This feature is also available in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "The Enterprise Edition is required to use this feature. This feature is also available in Pangolin Cloud.", "certResolver": "Certificate Resolver", "certResolverDescription": "Select the certificate resolver to use for this resource.", "selectCertResolver": "Select Certificate Resolver", diff --git a/messages/es-ES.json b/messages/es-ES.json index 55148ff0..28c0e779 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumentar el número de sitios", "idpManage": "Administrar proveedores de identidad", "idpManageDescription": "Ver y administrar proveedores de identidad en el sistema", + "idpGlobalModeBanner": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el panel de administración. Para habilitar los IdPs por organización, edita la configuración del servidor y establece el modo de IdP en org. Consulta la documentación. Si deseas seguir utilizando IdPs globales y hacer que esto desaparezca de las configuraciones de la organización, establece explícitamente el modo en global en la configuración.", + "idpGlobalModeBannerUpgradeRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el panel de administración. Para usar proveedores de identidad por organización, debes actualizar a la edición Empresarial.", + "idpGlobalModeBannerLicenseRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando identificadores globales (compartidos en todas las organizaciones). Gestionar identificaciones globales en el panel de administración. Para utilizar proveedores de identidad por organización, se requiere una licencia de empresa.", "idpDeletedDescription": "Proveedor de identidad eliminado correctamente", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin del año siguiente", "actionLogsDescription": "Ver un historial de acciones realizadas en esta organización", "accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización", - "licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.", - "ossEnterpriseEditionRequired": "La Enterprise Edition es necesaria para utilizar esta función.", + "licenseRequiredToUse": "Se requiere una licencia Enterprise Edition para utilizar esta función. Esta característica también está disponible en Pangolin Cloud.", + "ossEnterpriseEditionRequired": "La versión Enterprise es necesaria para utilizar esta función. Esta función también está disponible en Pangolin Cloud.", "certResolver": "Resolver certificado", "certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.", "selectCertResolver": "Seleccionar Resolver Certificado", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index f7914e65..9b2e24d2 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Augmenter le nombre de sites", "idpManage": "Gérer les fournisseurs d'identité", "idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système", + "idpGlobalModeBanner": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérez les IdPs globaux dans le panneau d'administration . Pour activer les IdPs par organisation, éditez la configuration du serveur et réglez le mode IdP sur org. Voir la documentation. Si vous voulez continuer à utiliser les IdPs globaux et faire disparaître cela des paramètres de l'organisation, définissez explicitement le mode à global dans la configuration.", + "idpGlobalModeBannerUpgradeRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration . Pour utiliser les fournisseurs d'identité par organisation, vous devez passer à l'édition Entreprise.", + "idpGlobalModeBannerLicenseRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration . Pour utiliser les fournisseurs d'identité par organisation, une licence d'entreprise est requise.", "idpDeletedDescription": "Fournisseur d'identité supprimé avec succès", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fin de l'année suivante", "actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation", "accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation", - "licenseRequiredToUse": "Une licence Entreprise est nécessaire pour utiliser cette fonctionnalité.", - "ossEnterpriseEditionRequired": "La version Enterprise Edition est requise pour utiliser cette fonctionnalité.", + "licenseRequiredToUse": "Une licence Enterprise Edition est nécessaire pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans Pangolin Cloud.", + "ossEnterpriseEditionRequired": "La version Enterprise Edition est requise pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans Pangolin Cloud.", "certResolver": "Résolveur de certificat", "certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.", "selectCertResolver": "Sélectionnez le résolveur de certificat", diff --git a/messages/it-IT.json b/messages/it-IT.json index 8f9d0352..30443e98 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumenta conteggio siti", "idpManage": "Gestisci Provider di Identità", "idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema", + "idpGlobalModeBanner": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Sta utilizzando IdP globali (condivisi in tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione . Per abilitare IdP per organizzazione, modificare la configurazione del server e impostare la modalità IdP su org. Vedere i documenti. Se si desidera continuare a utilizzare IdP globali e far sparire questo dalle impostazioni dell'organizzazione, impostare esplicitamente la modalità globale nella configurazione.", + "idpGlobalModeBannerUpgradeRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci gli IdP globali nel pannello di amministrazione . Per utilizzare i provider di identità per organizzazione, è necessario aggiornare all'edizione Enterprise.", + "idpGlobalModeBannerLicenseRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione . Per utilizzare provider di identità per organizzazione, è richiesta una licenza Enterprise.", "idpDeletedDescription": "Provider di identità eliminato con successo", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fine dell'anno successivo", "actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione", "accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione", - "licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise.", - "ossEnterpriseEditionRequired": "L' Enterprise Edition è necessaria per utilizzare questa funzione.", + "licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise Edition . Questa funzionalità è disponibile anche in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "L' Enterprise Edition è necessaria per utilizzare questa funzione. Questa funzionalità è disponibile anche in Pangolin Cloud.", "certResolver": "Risolutore Di Certificato", "certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.", "selectCertResolver": "Seleziona Risolutore Di Certificato", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 9d2ccad6..13f0a22a 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "사이트 수 증가", "idpManage": "아이덴티티 공급자 관리", "idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다", + "idpGlobalModeBanner": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 IdP를 활성화하려면 서버 설정을 편집하고 IdP 모드를 조직으로 설정하십시오. 문서 보기. 글로벌 IdP 사용을 계속하고 조직 설정에서 이 항목을 제거하려면 설정에서 모드를 글로벌로 명시적으로 설정하십시오.", + "idpGlobalModeBannerUpgradeRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 Enterprise 에디션으로 업그레이드해야 합니다.", + "idpGlobalModeBannerLicenseRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. 관리자 패널에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 엔터프라이즈 라이선스가 필요합니다.", "idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "다음 연도 말", "actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다", "accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다", - "licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.", - "ossEnterpriseEditionRequired": "이 기능을 사용하려면 Enterprise Edition이 필요합니다.", + "licenseRequiredToUse": "이 기능을 사용하려면 엔터프라이즈 에디션 라이선스가 필요합니다. 이 기능은 판골린 클라우드에서도 사용할 수 있습니다.", + "ossEnterpriseEditionRequired": "이 기능을 사용하려면 엔터프라이즈 에디션이 필요합니다. 이 기능은 판골린 클라우드에서도 사용할 수 있습니다.", "certResolver": "인증서 해결사", "certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.", "selectCertResolver": "인증서 해결사 선택", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index ca6fa2cb..afb9af38 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Øk antall områder", "idpManage": "Administrer Identitetsleverandører", "idpManageDescription": "Vis og administrer identitetsleverandører i systemet", + "idpGlobalModeBanner": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdP (delt over alle organisasjoner). Administrer globale IdP'er i admin-panelet. For å aktivere IdP per organisasjon, rediger serverkonfigurasjonen og sett IdP-modus til org. Se dokumentasjonen. Hvis du vil fortsette å bruke globale IdPs og få denne til å forsvinne fra organisasjonens innstillinger, satt eksplisitt modusen til global i konfigurasjonen.", + "idpGlobalModeBannerUpgradeRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i administrasjons-panelet. For å bruke identitetsleverandører per organisasjon, må du oppgradere til Enterprise-utgaven.", + "idpGlobalModeBannerLicenseRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i administrasjons-panelet. For å bruke identitetsleverandører per organisasjon, kreves en Enterprise-lisens.", "idpDeletedDescription": "Identitetsleverandør slettet vellykket", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Slutt på neste år", "actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen", "accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen", - "licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.", - "ossEnterpriseEditionRequired": "Enterprise Edition er nødvendig for å bruke denne funksjonen.", + "licenseRequiredToUse": "En Enterprise Edition lisens er påkrevd for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i Pangolin Cloud.", "certResolver": "Sertifikat løser", "certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.", "selectCertResolver": "Velg sertifikatløser", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 76c08e81..78783d92 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Toename van site vergroten", "idpManage": "Identiteitsaanbieders beheren", "idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren", + "idpGlobalModeBanner": "Identiteitsaanbieders (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld tussen alle organisaties). Beheer globale IdPs in het beheerderspaneel. Om IdPs per organisatie in te schakelen, bewerk de server configuratie en zet IdP modus op org. Zie de documenten. Als je globale IdPs wilt blijven gebruiken en dit uit de organisatie-instellingen wilt laten verdwijnen, zet dan expliciet de modus naar globaal in de config.", + "idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het beheerderspaneel. Om identiteitsproviders per organisatie te gebruiken, moet u upgraden naar de Enterprise editie.", + "idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het beheerderspaneel. Om identiteitsaanbieders per organisatie te gebruiken, is een Enterprise-licentie vereist.", "idpDeletedDescription": "Identity provider succesvol verwijderd", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Einde van volgend jaar", "actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie", "accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken", - "licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.", - "ossEnterpriseEditionRequired": "De Enterprise Edition is vereist om deze functie te gebruiken.", + "licenseRequiredToUse": "Een Enterprise Edition licentie is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in Pangolin Cloud.", + "ossEnterpriseEditionRequired": "De Enterprise Edition is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in Pangolin Cloud.", "certResolver": "Certificaat Resolver", "certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.", "selectCertResolver": "Certificaat Resolver selecteren", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index d2cf4ac8..b3d3cd94 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Zwiększ liczbę witryn", "idpManage": "Zarządzaj dostawcami tożsamości", "idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie", + "idpGlobalModeBanner": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używa globalnych IdP (współdzielonych ze wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby włączyć IdP na organizację, edytuj konfigurację serwera i ustaw tryb IdP na org. Zobacz dokumentację. Jeśli chcesz nadal używać globalnych IdP i sprawić, że zniknie to z ustawień organizacji, wyraźnie ustaw tryb globalny w konfiguracji.", + "idpGlobalModeBannerUpgradeRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby korzystać z dostawców tożsamości na organizację, musisz zaktualizować do edycji Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym . Aby korzystać z dostawców tożsamości na organizację, wymagana jest licencja Enterprise.", "idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Koniec następnego roku", "actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji", "accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji", - "licenseRequiredToUse": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.", - "ossEnterpriseEditionRequired": "Enterprise Edition jest wymagany do korzystania z tej funkcji.", + "licenseRequiredToUse": "Do korzystania z tej funkcji wymagana jest licencja Enterprise Edition . Ta funkcja jest również dostępna w Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Enterprise Edition jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w Pangolin Cloud.", "certResolver": "Rozwiązywanie certyfikatów", "certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.", "selectCertResolver": "Wybierz Resolver certyfikatów", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 6dd068f2..e7985138 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Aumentar contagem de sites", "idpManage": "Gerir Provedores de Identidade", "idpManageDescription": "Visualizar e gerir provedores de identidade no sistema", + "idpGlobalModeBanner": "Provedores de identidade (Pds) por organização estão desabilitados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie IdPs no painel admin. Para habilitar IdPs por organização, edite a configuração do servidor e defina o modo IdP como org. Veja a documentação. Se quiser continuar usando IdPs globais e fazer isso desaparecer das configurações da organização, defina explicitamente o modo como global na configuração.", + "idpGlobalModeBannerUpgradeRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no painel administrativo. Para usar provedores de identidade por organização, você deve atualizar para a edição Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no painel administrativo. Para usar provedores de identidade por organização, é necessário uma licença Enterprise.", "idpDeletedDescription": "Provedor de identidade eliminado com sucesso", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Fim do ano seguinte", "actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização", "accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização", - "licenseRequiredToUse": "É necessária uma licença empresarial para usar esse recurso.", - "ossEnterpriseEditionRequired": "O Enterprise Edition é necessário para usar este recurso.", + "licenseRequiredToUse": "Uma licença Enterprise Edition é necessária para usar este recurso. Este recurso também está disponível no Pangolin Cloud.", + "ossEnterpriseEditionRequired": "O Enterprise Edition é necessário para usar este recurso. Este recurso também está disponível no Pangolin Cloud.", "certResolver": "Resolvedor de Certificado", "certResolverDescription": "Selecione o resolvedor de certificados para este recurso.", "selectCertResolver": "Selecionar solucionador de certificado", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index b54bcc94..4891e0a9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Увеличить количество сайтов", "idpManage": "Управление поставщиками удостоверений", "idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе", + "idpGlobalModeBanner": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Чтобы включить IdP для каждой организации, отредактируйте конфигурацию сервера и установите режим IdP в org. См. документацию. Если вы хотите продолжать использовать глобальные IdP и скрыть это из настроек организации, явно установите режим в глобальном конфиге.", + "idpGlobalModeBannerUpgradeRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Чтобы использовать поставщиков удостоверений для каждой организации, необходимо обновить систему до версии Enterprise.", + "idpGlobalModeBannerLicenseRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в админ-панели. Для использования поставщиков удостоверений на организацию требуется лицензия Enterprise.", "idpDeletedDescription": "Поставщик удостоверений успешно удалён", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Конец следующего года", "actionLogsDescription": "Просмотр истории действий, выполненных в этой организации", "accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации", - "licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.", - "ossEnterpriseEditionRequired": "Для использования этой функции требуется корпоративная версия .", + "licenseRequiredToUse": "Лицензия на Enterprise Edition требуется для использования этой функции. Эта функция также доступна в Pangolin Cloud.", + "ossEnterpriseEditionRequired": "Для использования этой функции требуется Enterprise Edition. Эта функция также доступна в Pangolin Cloud.", "certResolver": "Резольвер сертификата", "certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.", "selectCertResolver": "Выберите резолвер сертификата", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 207de8aa..0c4c921d 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "Site sayısını artır", "idpManage": "Kimlik Sağlayıcılarını Yönet", "idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin", + "idpGlobalModeBanner": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına IdP'leri etkinleştirmek için, sunucu yapılandırmasını düzenleyin ve IdP modunu 'org' olarak ayarlayın. Belgeleri inceleyin . Küresel IdP'leri kullanmaya devam etmek istiyorsanız ve bunun örgüt ayarlarından kaybolmasını istiyorsanız, yapılandırmada modu otomatik olarak 'global' olarak ayarlayın.", + "idpGlobalModeBannerUpgradeRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için, Enterprise sürümüne yükseltmeniz gerekmektedir.", + "idpGlobalModeBannerLicenseRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri yönetici panelinde yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için Enterprise lisansı gereklidir.", "idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu", "actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin", "accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin", - "licenseRequiredToUse": "Bu özelliği kullanmak için bir kurumsal lisans gereklidir.", - "ossEnterpriseEditionRequired": "Bu özelliği kullanmak için Kurumsal Sürüm gereklidir.", + "licenseRequiredToUse": "Bu özelliği kullanmak için bir Enterprise Edition lisansı gereklidir. Bu özellik ayrıca Pangolin Cloud'da da mevcuttur.", + "ossEnterpriseEditionRequired": "Bu özelliği kullanmak için Enterprise Edition gereklidir. Bu özellik ayrıca Pangolin Cloud'da da mevcuttur.", "certResolver": "Sertifika Çözücü", "certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.", "selectCertResolver": "Sertifika Çözücü Seçin", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index c36433b3..7312ba32 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -791,6 +791,9 @@ "sitestCountIncrease": "增加站点数量", "idpManage": "管理身份提供商", "idpManageDescription": "查看和管理系统中的身份提供商", + "idpGlobalModeBanner": "此服务器上禁用了每个组织的身份提供商(Idps)。 它正在使用全局IdP(所有组织共享)。在 管理面板中管理全局IdP。 要启用每个组织的 IdP,请编辑服务器配置并将 IdP 模式设置为 org。 请参阅文档。 如果您想要继续使用全局IdP并使其从组织设置中消失,请在配置中将模式设置为全局模式。", + "idpGlobalModeBannerUpgradeRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 管理面板管理全局身份。要使用每个组织的身份提供者,您必须升级到企业版本。", + "idpGlobalModeBannerLicenseRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 管理面板管理全局身份。要使用每个组织的身份提供者,需要企业许可证。", "idpDeletedDescription": "身份提供商删除成功", "idpOidc": "OAuth2/OIDC", "idpQuestionRemove": "您确定要永久删除身份提供者吗?", @@ -2280,8 +2283,8 @@ "logRetentionEndOfFollowingYear": "下一年结束", "actionLogsDescription": "查看此机构执行的操作历史", "accessLogsDescription": "查看此机构资源的访问认证请求", - "licenseRequiredToUse": "需要企业许可证才能使用此功能。", - "ossEnterpriseEditionRequired": "需要 Enterprise Edition 才能使用此功能。", + "licenseRequiredToUse": "需要 Enterprise Edition 许可才能使用此功能。此功能也可在 Pangolin Cloud 中使用。", + "ossEnterpriseEditionRequired": "Enterprise Edition 需要使用此功能。此功能也可在 Pangolin Cloud 中使用。", "certResolver": "证书解决器", "certResolverDescription": "选择用于此资源的证书解析器。", "selectCertResolver": "选择证书解析", diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 265cee10..40f6d713 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -79,6 +79,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", { subscriptionItemId: integer("subscriptionItemId").primaryKey({ autoIncrement: true }), + stripeSubscriptionItemId: text("stripeSubscriptionItemId"), subscriptionId: text("subscriptionId") .notNull() .references(() => subscriptions.subscriptionId, { diff --git a/server/lib/consts.ts b/server/lib/consts.ts index b6758c15..018e017e 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -2,7 +2,7 @@ import path from "path"; import { fileURLToPath } from "url"; // This is a placeholder value replaced by the build process -export const APP_VERSION = "1.15.0"; +export const APP_VERSION = "1.15.3"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/private/lib/config.ts b/server/private/lib/config.ts index a1baf5a6..8e635c93 100644 --- a/server/private/lib/config.ts +++ b/server/private/lib/config.ts @@ -65,6 +65,11 @@ export class PrivateConfig { this.rawPrivateConfig.branding?.logo?.dark_path || undefined; } + if (this.rawPrivateConfig.app.identity_provider_mode) { + process.env.IDENTITY_PROVIDER_MODE = + this.rawPrivateConfig.app.identity_provider_mode; + } + process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding ?.logo?.auth_page?.width ? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString() @@ -129,10 +134,6 @@ export class PrivateConfig { process.env.USE_PANGOLIN_DNS = this.rawPrivateConfig.flags.use_pangolin_dns.toString(); } - if (this.rawPrivateConfig.flags.use_org_only_idp) { - process.env.USE_ORG_ONLY_IDP = - this.rawPrivateConfig.flags.use_org_only_idp.toString(); - } } public getRawPrivateConfig() { diff --git a/server/private/lib/readConfigFile.ts b/server/private/lib/readConfigFile.ts index ac528e73..e5efa498 100644 --- a/server/private/lib/readConfigFile.ts +++ b/server/private/lib/readConfigFile.ts @@ -25,7 +25,8 @@ export const privateConfigSchema = z.object({ app: z .object({ region: z.string().optional().default("default"), - base_domain: z.string().optional() + base_domain: z.string().optional(), + identity_provider_mode: z.enum(["global", "org"]).optional() }) .optional() .default({ @@ -95,7 +96,7 @@ export const privateConfigSchema = z.object({ .object({ enable_redis: z.boolean().optional().default(false), use_pangolin_dns: z.boolean().optional().default(false), - use_org_only_idp: z.boolean().optional().default(false), + use_org_only_idp: z.boolean().optional() }) .optional() .prefault({}), @@ -181,7 +182,29 @@ export const privateConfigSchema = z.object({ // localFilePath: z.string().optional() }) .optional() -}); +}) + .transform((data) => { + // this to maintain backwards compatibility with the old config file + const identityProviderMode = data.app?.identity_provider_mode; + const useOrgOnlyIdp = data.flags?.use_org_only_idp; + + if (identityProviderMode !== undefined) { + return data; + } + if (useOrgOnlyIdp === true) { + return { + ...data, + app: { ...data.app, identity_provider_mode: "org" as const } + }; + } + if (useOrgOnlyIdp === false) { + return { + ...data, + app: { ...data.app, identity_provider_mode: "global" as const } + }; + } + return data; + }); export function readPrivateConfigFile() { if (build == "oss") { diff --git a/server/private/routers/orgIdp/createOrgOidcIdp.ts b/server/private/routers/orgIdp/createOrgOidcIdp.ts index d1874033..77346fd9 100644 --- a/server/private/routers/orgIdp/createOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/createOrgOidcIdp.ts @@ -27,6 +27,7 @@ import config from "@server/lib/config"; import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types"; import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import privateConfig from "#private/lib/config"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() }); @@ -92,6 +93,18 @@ export async function createOrgOidcIdp( ); } + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + const { clientId, clientSecret, diff --git a/server/private/routers/orgIdp/deleteOrgIdp.ts b/server/private/routers/orgIdp/deleteOrgIdp.ts index 176f4238..2d6b0899 100644 --- a/server/private/routers/orgIdp/deleteOrgIdp.ts +++ b/server/private/routers/orgIdp/deleteOrgIdp.ts @@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error"; import { idp, idpOidcConfig, idpOrg } from "@server/db"; import { eq } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; +import privateConfig from "#private/lib/config"; const paramsSchema = z .object({ @@ -59,6 +60,18 @@ export async function deleteOrgIdp( const { idpId } = parsedParams.data; + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + // Check if IDP exists const [existingIdp] = await db .select() diff --git a/server/private/routers/orgIdp/updateOrgOidcIdp.ts b/server/private/routers/orgIdp/updateOrgOidcIdp.ts index c5619460..804afbe6 100644 --- a/server/private/routers/orgIdp/updateOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/updateOrgOidcIdp.ts @@ -26,6 +26,7 @@ import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; import { isSubscribed } from "#private/lib/isSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import privateConfig from "#private/lib/config"; const paramsSchema = z .object({ @@ -97,6 +98,18 @@ export async function updateOrgOidcIdp( ); } + if ( + privateConfig.getRawPrivateConfig().app.identity_provider_mode !== + "org" + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature." + ) + ); + } + const { idpId, orgId } = parsedParams.data; const { clientId, diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index 78764eac..4eafb061 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -26,6 +26,7 @@ import { generateId } from "@server/auth/sessions/app"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { getUniqueClientName } from "@server/db/names"; +import { build } from "@server/build"; const createClientParamsSchema = z.strictObject({ orgId: z.string() @@ -195,6 +196,12 @@ export async function createClient( const randomExitNode = exitNodesList[Math.floor(Math.random() * exitNodesList.length)]; + if (!randomExitNode) { + return next( + createHttpError(HttpCode.NOT_FOUND, `No exit nodes available. ${build == "saas" ? "Please contact support." : "You need to install gerbil to use the clients."}`) + ); + } + const [adminRole] = await trx .select() .from(roles) diff --git a/server/setup/scriptsSqlite/1.15.3.ts b/server/setup/scriptsSqlite/1.15.3.ts index 614b0759..3ba8fb09 100644 --- a/server/setup/scriptsSqlite/1.15.3.ts +++ b/server/setup/scriptsSqlite/1.15.3.ts @@ -14,6 +14,7 @@ export default async function migration() { db.transaction(() => { db.prepare(`ALTER TABLE 'limits' ADD 'override' integer DEFAULT false;`).run(); db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'featureId' text;`).run(); + db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'stripeSubscriptionItemId' text;`).run(); db.prepare(`ALTER TABLE 'subscriptions' ADD 'version' integer;`).run(); db.prepare(`ALTER TABLE 'subscriptions' ADD 'type' text;`).run(); })(); diff --git a/src/app/[orgId]/settings/(private)/idp/page.tsx b/src/app/[orgId]/settings/(private)/idp/page.tsx index 6d75c0a5..cd0bc556 100644 --- a/src/app/[orgId]/settings/(private)/idp/page.tsx +++ b/src/app/[orgId]/settings/(private)/idp/page.tsx @@ -5,6 +5,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import IdpTable, { IdpRow } from "@app/components/OrgIdpTable"; import { getTranslations } from "next-intl/server"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; +import { IdpGlobalModeBanner } from "@app/components/IdpGlobalModeBanner"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; type OrgIdpPageProps = { @@ -36,6 +37,8 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) { description={t("idpManageDescription")} /> + + diff --git a/src/app/[orgId]/settings/general/security/page.tsx b/src/app/[orgId]/settings/general/security/page.tsx index a7b5da03..2c51e9ec 100644 --- a/src/app/[orgId]/settings/general/security/page.tsx +++ b/src/app/[orgId]/settings/general/security/page.tsx @@ -44,7 +44,6 @@ import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import type { OrgContextType } from "@app/contexts/orgContext"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; -import { isAppPageRouteDefinition } from "next/dist/server/route-definitions/app-page-route-definition"; // Session length options in hours const SESSION_LENGTH_OPTIONS = [ @@ -219,6 +218,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { @@ -314,6 +317,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { @@ -411,6 +418,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) { {LOG_RETENTION_OPTIONS.filter( (option) => { + if (build != "saas") { + return true; + } + let maxDays: number; if (!subscriptionTier) { diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx index 121e7196..b00ce1ee 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx @@ -132,7 +132,7 @@ export default function ResourceAuthenticationPage() { const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery( orgQueries.identityProviders({ orgId: org.org.orgId, - useOrgOnlyIdp: env.flags.useOrgOnlyIdp + useOrgOnlyIdp: env.app.identityProviderMode === "org" }) ); diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index c84a077f..7368cb25 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -50,7 +50,7 @@ export default async function UsersPage(props: PageProps) { title={t("userTitle")} description={t("userDescription")} /> - + {t("userAbount")} diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 2ba4d7f8..bfb552df 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -76,12 +76,13 @@ export default async function Page(props: { // Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled) const useSmartLogin = - build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp); + build === "saas" || + (build === "enterprise" && env.app.identityProviderMode === "org"); let loginIdps: LoginFormIDP[] = []; if (!useSmartLogin) { // Load IdPs for DashboardLoginForm (OSS or org-only IdP mode) - if (build === "oss" || !env.flags.useOrgOnlyIdp) { + if (build === "oss" || env.app.identityProviderMode !== "org") { const idpsRes = await cache( async () => await priv.get>("/idp") @@ -165,7 +166,8 @@ export default async function Page(props: { forceLogin={forceLogin} showOrgLogin={ !isInvite && - (build === "saas" || env.flags.useOrgOnlyIdp) + (build === "saas" || + env.app.identityProviderMode === "org") } searchParams={searchParams} defaultUser={defaultUser} @@ -188,7 +190,8 @@ export default async function Page(props: {

)} - {!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? ( + {!isInvite && + (build === "saas" || env.app.identityProviderMode === "org") ? ( diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index d74ef30b..7df4364a 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -124,7 +124,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ // PaidFeaturesAlert ...((build === "oss" && !env?.flags.disableEnterpriseFeatures) || build === "saas" || - env?.flags.useOrgOnlyIdp + env?.app.identityProviderMode === "org" || + (env?.app.identityProviderMode === undefined && build !== "oss") ? [ { title: "sidebarIdentityProviders", @@ -251,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [ href: "/admin/api-keys", icon: }, - ...(build === "oss" || !env?.flags.useOrgOnlyIdp + ...(build === "oss" || + env?.app.identityProviderMode === "global" || + env?.app.identityProviderMode === undefined ? [ { title: "sidebarIdentityProviders", diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index daceb2fa..a1998062 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -322,7 +322,7 @@ export default function AuthPageBrandingForm({ {build === "saas" || - env.env.flags.useOrgOnlyIdp ? ( + env.env.app.identityProviderMode === "org" ? ( <>
diff --git a/src/components/IdpGlobalModeBanner.tsx b/src/components/IdpGlobalModeBanner.tsx new file mode 100644 index 00000000..9f864b36 --- /dev/null +++ b/src/components/IdpGlobalModeBanner.tsx @@ -0,0 +1,65 @@ +"use client"; + +import Link from "next/link"; +import { useTranslations } from "next-intl"; +import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { Info } from "lucide-react"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { build } from "@server/build"; + +export function IdpGlobalModeBanner() { + const t = useTranslations(); + const { env } = useEnvContext(); + const { isPaidUser, hasEnterpriseLicense } = usePaidStatus(); + + const identityProviderModeUndefined = + env.app.identityProviderMode === undefined; + const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc); + const enterpriseUnlicensed = + build === "enterprise" && !hasEnterpriseLicense; + + if (build === "saas") { + return null; + } + + if (!identityProviderModeUndefined) { + return null; + } + + const adminPanelLinkRenderer = (chunks: React.ReactNode) => ( + + {chunks} + + ); + + return ( + + + + {paidUserForOrgOidc + ? t.rich("idpGlobalModeBanner", { + adminPanelLink: adminPanelLinkRenderer, + configDocsLink: (chunks) => ( + + {chunks} + + ) + }) + : enterpriseUnlicensed + ? t.rich("idpGlobalModeBannerLicenseRequired", { + adminPanelLink: adminPanelLinkRenderer + }) + : t.rich("idpGlobalModeBannerUpgradeRequired", { + adminPanelLink: adminPanelLinkRenderer + })} + + + ); +} diff --git a/src/components/PaidFeaturesAlert.tsx b/src/components/PaidFeaturesAlert.tsx index f8a4223b..f4bc528d 100644 --- a/src/components/PaidFeaturesAlert.tsx +++ b/src/components/PaidFeaturesAlert.tsx @@ -38,15 +38,49 @@ const bannerContentClassName = "py-3 px-4"; const bannerRowClassName = "flex items-center gap-2.5 text-sm text-muted-foreground"; const bannerIconClassName = "size-4 shrink-0 text-purple-500"; +const docsLinkClassName = + "inline-flex items-center gap-1 font-medium text-purple-600 underline"; +const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/"; +const ENTERPRISE_DOCS_URL = + "https://docs.pangolin.net/self-host/enterprise-edition"; function getTierLinkRenderer(billingHref: string) { return function tierLinkRenderer(chunks: React.ReactNode) { + return ( + + {chunks} + + ); + }; +} + +function getPangolinCloudLinkRenderer() { + return function pangolinCloudLinkRenderer(chunks: React.ReactNode) { return ( {chunks} + + + ); + }; +} + +function getDocsLinkRenderer(href: string) { + return function docsLinkRenderer(chunks: React.ReactNode) { + return ( + + {chunks} + ); }; @@ -66,6 +100,8 @@ export function PaidFeaturesAlert({ tiers }: Props) { const requiredTierName = requiredTier ? t(TIER_TRANSLATION_KEYS[requiredTier]) : null; const billingHref = orgId ? `/${orgId}/settings/billing` : "https://pangolin.net/pricing"; const tierLinkRenderer = getTierLinkRenderer(billingHref); + const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer(); + const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL); if (env.flags.disableEnterpriseFeatures) { return null; @@ -103,7 +139,12 @@ export function PaidFeaturesAlert({ tiers }: Props) {
- {t("licenseRequiredToUse")} + + {t.rich("licenseRequiredToUse", { + enterpriseLicenseLink: enterpriseDocsLinkRenderer, + pangolinCloudLink: pangolinCloudLinkRenderer + })} +
@@ -116,17 +157,8 @@ export function PaidFeaturesAlert({ tiers }: Props) { {t.rich("ossEnterpriseEditionRequired", { - enterpriseEditionLink: (chunks) => ( - - {chunks} - - - ) + enterpriseEditionLink: enterpriseDocsLinkRenderer, + pangolinCloudLink: pangolinCloudLinkRenderer })}
diff --git a/src/components/PermissionsSelectBox.tsx b/src/components/PermissionsSelectBox.tsx index 73f8a212..b11c635a 100644 --- a/src/components/PermissionsSelectBox.tsx +++ b/src/components/PermissionsSelectBox.tsx @@ -118,7 +118,7 @@ function getActionsCategories(root: boolean) { } }; - if (root || build === "saas" || env.flags.useOrgOnlyIdp) { + if (root || build === "saas" || env.app.identityProviderMode === "org") { actionsByCategory["Identity Provider (IDP)"] = { [t("actionCreateIdp")]: "createIdp", [t("actionUpdateIdp")]: "updateIdp", diff --git a/src/components/SignupForm.tsx b/src/components/SignupForm.tsx index 9cb93757..a54b1c23 100644 --- a/src/components/SignupForm.tsx +++ b/src/components/SignupForm.tsx @@ -204,7 +204,9 @@ export default function SignupForm({ ? env.branding.logo?.authPage?.height || 44 : 44; - const showOrgBanner = fromSmartLogin && (build === "saas" || env.flags.useOrgOnlyIdp); + const showOrgBanner = + fromSmartLogin && + (build === "saas" || env.app.identityProviderMode === "org"); const orgBannerHref = redirect ? `/auth/org?redirect=${encodeURIComponent(redirect)}` : "/auth/org"; @@ -226,388 +228,398 @@ export default function SignupForm({
)} - -
- -
-
-

{getSubtitle()}

-
-
- -
- - ( - - {t("email")} - - - - - - )} - /> - ( - -
- {t("password")} - {passwordStrength.strength === - "strong" && ( - - )} -
- -
+ +
+ +
+
+

{getSubtitle()}

+
+
+ + + + ( + + {t("email")} + { - field.onChange(e); - setPasswordValue( - e.target.value - ); - }} - className={cn( - passwordStrength.strength === - "strong" && - "border-green-500 focus-visible:ring-green-500", - passwordStrength.strength === - "medium" && - "border-yellow-500 focus-visible:ring-yellow-500", - passwordStrength.strength === - "weak" && - passwordValue.length > - 0 && - "border-red-500 focus-visible:ring-red-500" - )} - autoComplete="new-password" + disabled={!!emailParam} /> -
-
- - {passwordValue.length > 0 && ( -
- {/* Password Strength Meter */} -
-
- - {t("passwordStrength")} - - - {t( - `passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}` - )} - -
- -
- - {/* Requirements Checklist */} -
-
- {t("passwordRequirements")} -
-
-
- {passwordStrength - .requirements - .length ? ( - - ) : ( - - )} - - {t( - "passwordRequirementLengthText" - )} - -
-
- {passwordStrength - .requirements - .uppercase ? ( - - ) : ( - - )} - - {t( - "passwordRequirementUppercaseText" - )} - -
-
- {passwordStrength - .requirements - .lowercase ? ( - - ) : ( - - )} - - {t( - "passwordRequirementLowercaseText" - )} - -
-
- {passwordStrength - .requirements - .number ? ( - - ) : ( - - )} - - {t( - "passwordRequirementNumberText" - )} - -
-
- {passwordStrength - .requirements - .special ? ( - - ) : ( - - )} - - {t( - "passwordRequirementSpecialText" - )} - -
-
-
-
- )} - - {/* Only show FormMessage when not showing our custom requirements */} - {passwordValue.length === 0 && ( + - )} -
- )} - /> - ( - -
- - {t("confirmPassword")} - - {doPasswordsMatch && ( - - )} -
- -
- { - field.onChange(e); - setConfirmPasswordValue( - e.target.value - ); - }} - className={cn( - doPasswordsMatch && - "border-green-500 focus-visible:ring-green-500", - confirmPasswordValue.length > - 0 && - !doPasswordsMatch && - "border-red-500 focus-visible:ring-red-500" - )} - autoComplete="new-password" - /> + + )} + /> + ( + +
+ + {t("password")} + + {passwordStrength.strength === + "strong" && ( + + )}
- - {confirmPasswordValue.length > 0 && - !doPasswordsMatch && ( -

- {t("passwordsDoNotMatch")} -

- )} - {/* Only show FormMessage when field is empty */} - {confirmPasswordValue.length === 0 && ( - - )} -
- )} - /> - {build === "saas" && ( - <> - ( - - - { - field.onChange(checked); - handleTermsChange( - checked as boolean + +
+ { + field.onChange(e); + setPasswordValue( + e.target.value ); }} - /> - -
- -
- {t( - "signUpTerms.IAgreeToThe" - )}{" "} - - {t( - "signUpTerms.termsOfService" - )}{" "} - - {t("signUpTerms.and")}{" "} - - {t( - "signUpTerms.privacyPolicy" - )} - -
-
- -
- - )} - /> - ( - - - - -
- - {t( - "signUpMarketing.keepMeInTheLoop" + className={cn( + passwordStrength.strength === + "strong" && + "border-green-500 focus-visible:ring-green-500", + passwordStrength.strength === + "medium" && + "border-yellow-500 focus-visible:ring-yellow-500", + passwordStrength.strength === + "weak" && + passwordValue.length > + 0 && + "border-red-500 focus-visible:ring-red-500" )} - - + autoComplete="new-password" + />
-
- )} - /> - - )} + - {error && ( - - {error} - - )} + {passwordValue.length > 0 && ( +
+ {/* Password Strength Meter */} +
+
+ + {t( + "passwordStrength" + )} + + + {t( + `passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}` + )} + +
+ +
- - - - - + {/* Requirements Checklist */} +
+
+ {t( + "passwordRequirements" + )} +
+
+
+ {passwordStrength + .requirements + .length ? ( + + ) : ( + + )} + + {t( + "passwordRequirementLengthText" + )} + +
+
+ {passwordStrength + .requirements + .uppercase ? ( + + ) : ( + + )} + + {t( + "passwordRequirementUppercaseText" + )} + +
+
+ {passwordStrength + .requirements + .lowercase ? ( + + ) : ( + + )} + + {t( + "passwordRequirementLowercaseText" + )} + +
+
+ {passwordStrength + .requirements + .number ? ( + + ) : ( + + )} + + {t( + "passwordRequirementNumberText" + )} + +
+
+ {passwordStrength + .requirements + .special ? ( + + ) : ( + + )} + + {t( + "passwordRequirementSpecialText" + )} + +
+
+
+
+ )} + + {/* Only show FormMessage when not showing our custom requirements */} + {passwordValue.length === 0 && ( + + )} + + )} + /> + ( + +
+ + {t("confirmPassword")} + + {doPasswordsMatch && ( + + )} +
+ +
+ { + field.onChange(e); + setConfirmPasswordValue( + e.target.value + ); + }} + className={cn( + doPasswordsMatch && + "border-green-500 focus-visible:ring-green-500", + confirmPasswordValue.length > + 0 && + !doPasswordsMatch && + "border-red-500 focus-visible:ring-red-500" + )} + autoComplete="new-password" + /> +
+
+ {confirmPasswordValue.length > 0 && + !doPasswordsMatch && ( +

+ {t("passwordsDoNotMatch")} +

+ )} + {/* Only show FormMessage when field is empty */} + {confirmPasswordValue.length === 0 && ( + + )} +
+ )} + /> + {build === "saas" && ( + <> + ( + + + { + field.onChange( + checked + ); + handleTermsChange( + checked as boolean + ); + }} + /> + +
+ +
+ {t( + "signUpTerms.IAgreeToThe" + )}{" "} + + {t( + "signUpTerms.termsOfService" + )}{" "} + + {t( + "signUpTerms.and" + )}{" "} + + {t( + "signUpTerms.privacyPolicy" + )} + +
+
+ +
+
+ )} + /> + ( + + + + +
+ + {t( + "signUpMarketing.keepMeInTheLoop" + )} + + +
+
+ )} + /> + + )} + + {error && ( + + {error} + + )} + + + + + + ); } diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index 298745b9..ddbd42c2 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -32,7 +32,11 @@ export function pullEnv(): Env { process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true" ? true : false - } + }, + identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as + | "org" + | "global" + | undefined }, email: { emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false @@ -64,8 +68,6 @@ export function pullEnv(): Env { process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true" ? true : false, - useOrgOnlyIdp: - process.env.USE_ORG_ONLY_IDP === "true" ? true : false, disableEnterpriseFeatures: process.env.DISABLE_ENTERPRISE_FEATURES === "true" ? true diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 925e4348..46513ae5 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -8,6 +8,7 @@ export type Env = { product_updates: boolean; new_releases: boolean; }; + identityProviderMode?: "global" | "org"; }; server: { externalPort: string; @@ -34,7 +35,6 @@ export type Env = { hideSupporterKey: boolean; usePangolinDns: boolean; disableProductHelpBanners: boolean; - useOrgOnlyIdp: boolean; disableEnterpriseFeatures: boolean; }; branding: {