From e12785edbf6efd65e2ac23f33a82e107acd0a151 Mon Sep 17 00:00:00 2001 From: Andrew Cuoco Date: Thu, 20 Nov 2025 02:24:05 -0800 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20add=20automated=20dev=E2=86=92main?= =?UTF-8?q?=20sync=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sync-dev-to-main.yml | 291 +++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 .github/workflows/sync-dev-to-main.yml diff --git a/.github/workflows/sync-dev-to-main.yml b/.github/workflows/sync-dev-to-main.yml new file mode 100644 index 0000000000..7467f9bba3 --- /dev/null +++ b/.github/workflows/sync-dev-to-main.yml @@ -0,0 +1,291 @@ +name: Sync dev to main + +# Automatically merge dev → main daily at 8 AM PST (16:00 UTC) +# Uses github-actions[bot] with bypass permissions to skip PR approval requirements +# Handles conflicts gracefully (logs warning, skips merge, doesn't fail) + +on: + schedule: + # Run daily at 8:00 AM PST (16:00 UTC) + - cron: '0 16 * * *' + + workflow_dispatch: # Allow manual triggering + +permissions: + contents: write # Push to main branch + issues: write # Create issues on conflict + actions: read # Check workflow status (optional) + +env: + SOURCE_BRANCH: dev + TARGET_BRANCH: main + # Power Automate webhook for Teams notifications + TEAMS_WEBHOOK_URL: "https://default097499ff179d4959ab0286d364125b.fc.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/a6dd70a2ec694cf6957a2620e1d89c23/triggers/manual/paths/invoke?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=B_MxbOdUi92JYpARbYIcaDNwUdSqILgO1ZV6T7vsQXU" + +jobs: + sync-branches: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history for merge + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check if dev has new commits + id: check-new-commits + run: | + git fetch origin ${{ env.SOURCE_BRANCH }} + git fetch origin ${{ env.TARGET_BRANCH }} + + # Get commit counts + COMMITS_AHEAD=$(git rev-list --count origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}) + + echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT + + if [ "$COMMITS_AHEAD" -eq 0 ]; then + echo "ℹ️ No new commits in ${{ env.SOURCE_BRANCH }} - nothing to sync" + echo "has_new_commits=false" >> $GITHUB_OUTPUT + else + echo "✅ Found $COMMITS_AHEAD new commit(s) in ${{ env.SOURCE_BRANCH }}" + echo "has_new_commits=true" >> $GITHUB_OUTPUT + fi + + - name: Check latest dev build status (non-blocking) + if: steps.check-new-commits.outputs.has_new_commits == 'true' + continue-on-error: true # Don't fail if this step fails + run: | + echo "ℹ️ Checking latest build status on ${{ env.SOURCE_BRANCH }} (informational only)..." + + # Get latest build workflow run on dev + BUILD_STATUS=$(gh run list --workflow="build-and-deploy.yml" \ + --branch ${{ env.SOURCE_BRANCH }} --limit 1 --json conclusion,status \ + --jq '.[0] | "\(.status):\(.conclusion)"' 2>/dev/null || echo "unknown:unknown") + + echo "Latest build status: $BUILD_STATUS" + + if [[ "$BUILD_STATUS" == *"failure"* ]]; then + echo "⚠️ WARNING: Latest ${{ env.SOURCE_BRANCH }} build failed" + echo "⚠️ Proceeding with merge anyway (bypass enabled)" + elif [[ "$BUILD_STATUS" == *"in_progress"* ]]; then + echo "ℹ️ Build is still running on ${{ env.SOURCE_BRANCH }}" + echo "ℹ️ Proceeding with merge anyway (bypass enabled)" + else + echo "✅ Latest build status: $BUILD_STATUS" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Attempt merge + id: merge + if: steps.check-new-commits.outputs.has_new_commits == 'true' + run: | + echo "🔄 Attempting to merge ${{ env.SOURCE_BRANCH }} → ${{ env.TARGET_BRANCH }}..." + + # Checkout target branch + git checkout ${{ env.TARGET_BRANCH }} + + # Attempt merge (don't fail on conflict) + set +e + git merge --no-edit --no-ff origin/${{ env.SOURCE_BRANCH }} 2>&1 | tee merge_output.txt + MERGE_EXIT_CODE=$? + set -e + + if [ $MERGE_EXIT_CODE -ne 0 ]; then + echo "❌ Merge conflict detected" + echo "conflict=true" >> $GITHUB_OUTPUT + + # Get conflicted files + git status --short | grep '^UU\|^AA\|^DD' > conflicts.txt || echo "Unable to detect specific files" > conflicts.txt + + # Abort the merge + git merge --abort + + echo "⚠️ Skipping automated merge due to conflicts" + exit 0 # Exit success to allow graceful handling + else + echo "✅ Merge completed successfully" + echo "conflict=false" >> $GITHUB_OUTPUT + fi + + - name: Push to main + if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false' + run: | + echo "📤 Pushing merged changes to ${{ env.TARGET_BRANCH }}..." + + # Push directly to main (bypass branch protection via github-actions[bot]) + git push origin ${{ env.TARGET_BRANCH }} + + echo "✅ Successfully synced ${{ env.SOURCE_BRANCH }} → ${{ env.TARGET_BRANCH }}" + + - name: Get merge details for notification + if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false' + id: merge-details + run: | + # Get commit details + COMMIT_COUNT=${{ steps.check-new-commits.outputs.commits_ahead }} + COMMIT_SHA=$(git rev-parse HEAD | cut -c1-7) + + # Get commit messages + git log --oneline origin/${{ env.TARGET_BRANCH }}~$COMMIT_COUNT..origin/${{ env.TARGET_BRANCH }} | head -10 > commits.txt + + echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT + echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT + + - name: Send success notification to Teams + if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false' + run: | + # Get commit messages (truncate if too long) + COMMITS=$(cat commits.txt | head -5 | sed 's/^/ - /') + + curl -H "Content-Type: application/json" -d '{ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + "summary": "Dev → Main Sync Successful", + "themeColor": "28a745", + "title": "✅ Automated Sync: dev → main", + "sections": [{ + "activityTitle": "Successfully merged '${{ steps.check-new-commits.outputs.commits_ahead }}' commits", + "activitySubtitle": "Repository: netwrix/docs", + "facts": [ + {"name": "Commits merged:", "value": "${{ steps.merge-details.outputs.commit_count }}"}, + {"name": "Latest commit:", "value": "${{ steps.merge-details.outputs.commit_sha }}"}, + {"name": "Triggered by:", "value": "${{ github.event_name }}"}, + {"name": "Workflow:", "value": "${{ github.workflow }}"} + ], + "text": "**Recent commits:**\n\n'"$(echo "$COMMITS" | sed 's/$/\\n/' | tr -d '\n')"'" + }], + "potentialAction": [{ + "@type": "OpenUri", + "name": "View Workflow Run", + "targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}] + }, { + "@type": "OpenUri", + "name": "View Repository", + "targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}"}] + }] + }' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification" + + - name: Create GitHub issue for conflict + if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true' + uses: actions/github-script@v7 + with: + script: | + const conflictedFiles = require('fs').readFileSync('conflicts.txt', 'utf8'); + const mergeOutput = require('fs').readFileSync('merge_output.txt', 'utf8'); + + const issue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '⚠️ Automated dev→main sync blocked by merge conflict', + labels: ['automated-sync', 'merge-conflict'], + body: '## Automated Sync Conflict\n\n' + + 'The automated sync from `dev` to `main` encountered merge conflicts and was skipped.\n\n' + + '**Workflow Run:** ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + '\n' + + '**Triggered by:** ' + context.eventName + '\n' + + '**Date:** ' + new Date().toISOString() + '\n\n' + + '### Conflicted Files\n\n' + + '```\n' + + conflictedFiles + '\n' + + '```\n\n' + + '### Merge Output\n\n' + + '```\n' + + mergeOutput + '\n' + + '```\n\n' + + '### Resolution Steps\n\n' + + '1. Manually resolve conflicts:\n' + + ' ```bash\n' + + ' git checkout main\n' + + ' git pull origin main\n' + + ' git merge origin/dev\n' + + ' # Resolve conflicts in your editor\n' + + ' git add .\n' + + ' git commit\n' + + ' git push origin main\n' + + ' ```\n\n' + + '2. Or create a PR from dev → main and resolve conflicts there\n\n' + + '3. Once resolved, the next scheduled sync will proceed normally\n\n' + + '---\n' + + '*This issue was created automatically by the sync-dev-to-main workflow.*' + }); + + console.log('Created issue #' + issue.data.number); + + - name: Send conflict notification to Teams + if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true' + run: | + curl -H "Content-Type: application/json" -d '{ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + "summary": "Dev → Main Sync Blocked by Conflict", + "themeColor": "ff9800", + "title": "⚠️ Automated Sync Blocked: Merge Conflict", + "sections": [{ + "activityTitle": "Manual intervention required", + "activitySubtitle": "Repository: netwrix/docs", + "facts": [ + {"name": "Source branch:", "value": "${{ env.SOURCE_BRANCH }}"}, + {"name": "Target branch:", "value": "${{ env.TARGET_BRANCH }}"}, + {"name": "Commits waiting:", "value": "${{ steps.check-new-commits.outputs.commits_ahead }}"}, + {"name": "Status:", "value": "Merge conflict detected"} + ], + "text": "The automated sync encountered merge conflicts. A GitHub issue has been created with details. Please resolve the conflicts manually." + }], + "potentialAction": [{ + "@type": "OpenUri", + "name": "View Workflow Run", + "targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}] + }, { + "@type": "OpenUri", + "name": "View Issues", + "targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/issues?q=is:issue+is:open+label:merge-conflict"}] + }] + }' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification" + + - name: Send skip notification to Teams + if: steps.check-new-commits.outputs.has_new_commits == 'false' + run: | + curl -H "Content-Type: application/json" -d '{ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + "summary": "No Changes to Sync", + "themeColor": "0078D4", + "title": "ℹ️ No Changes to Sync", + "sections": [{ + "activityTitle": "Branches are already in sync", + "activitySubtitle": "Repository: netwrix/docs", + "text": "No new commits found in dev branch. Nothing to sync." + }] + }' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification" + + - name: Workflow summary + if: always() + run: | + echo "## Sync Dev to Main - Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check-new-commits.outputs.has_new_commits }}" == "false" ]; then + echo "ℹ️ **Status:** No new commits to sync" >> $GITHUB_STEP_SUMMARY + echo "- Source: \`${{ env.SOURCE_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Target: \`${{ env.TARGET_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.merge.outputs.conflict }}" == "true" ]; then + echo "⚠️ **Status:** Merge conflict detected" >> $GITHUB_STEP_SUMMARY + echo "- Commits waiting: ${{ steps.check-new-commits.outputs.commits_ahead }}" >> $GITHUB_STEP_SUMMARY + echo "- Action: GitHub issue created" >> $GITHUB_STEP_SUMMARY + echo "- Resolution: Manual merge required" >> $GITHUB_STEP_SUMMARY + else + echo "✅ **Status:** Successfully synced" >> $GITHUB_STEP_SUMMARY + echo "- Commits merged: ${{ steps.merge-details.outputs.commit_count }}" >> $GITHUB_STEP_SUMMARY + echo "- Latest commit: \`${{ steps.merge-details.outputs.commit_sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Pushed to: \`${{ env.TARGET_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "*Automated by github-actions[bot] with branch protection bypass*" >> $GITHUB_STEP_SUMMARY From b0b2aee2f5c4ec47932ccd1efa1c60a4c0bf74b7 Mon Sep 17 00:00:00 2001 From: Andrew Cuoco Date: Thu, 20 Nov 2025 02:27:53 -0800 Subject: [PATCH 2/7] fix: use GitHub Secrets for Teams webhook URL (security fix) --- .github/workflows/sync-dev-to-main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-dev-to-main.yml b/.github/workflows/sync-dev-to-main.yml index 7467f9bba3..25d8d107c5 100644 --- a/.github/workflows/sync-dev-to-main.yml +++ b/.github/workflows/sync-dev-to-main.yml @@ -19,8 +19,8 @@ permissions: env: SOURCE_BRANCH: dev TARGET_BRANCH: main - # Power Automate webhook for Teams notifications - TEAMS_WEBHOOK_URL: "https://default097499ff179d4959ab0286d364125b.fc.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/a6dd70a2ec694cf6957a2620e1d89c23/triggers/manual/paths/invoke?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=B_MxbOdUi92JYpARbYIcaDNwUdSqILgO1ZV6T7vsQXU" + # Teams webhook URL stored in GitHub Secrets for security + TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }} jobs: sync-branches: From 2ab8f637b9a5b58d295dfb104222eb925ce123cb Mon Sep 17 00:00:00 2001 From: Andrew Cuoco Date: Thu, 20 Nov 2025 02:38:15 -0800 Subject: [PATCH 3/7] fix: wait for build workflow to complete before pushing to main - Modified workflow to wait for 'build-and-deploy.yml' to complete successfully on dev branch - Prevents 'Required status check build is in progress' error when pushing to main - Adds 30-minute timeout with 30-second polling interval - Fails gracefully if build fails, is cancelled, or times out This fixes the workflow execution failure where pushes to main were blocked because the required 'build' status check was still in progress. --- .github/workflows/sync-dev-to-main.yml | 66 ++++++++++++++++++-------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/.github/workflows/sync-dev-to-main.yml b/.github/workflows/sync-dev-to-main.yml index 25d8d107c5..22ef914c30 100644 --- a/.github/workflows/sync-dev-to-main.yml +++ b/.github/workflows/sync-dev-to-main.yml @@ -57,27 +57,55 @@ jobs: echo "has_new_commits=true" >> $GITHUB_OUTPUT fi - - name: Check latest dev build status (non-blocking) + - name: Wait for build workflow to complete if: steps.check-new-commits.outputs.has_new_commits == 'true' - continue-on-error: true # Don't fail if this step fails run: | - echo "ℹ️ Checking latest build status on ${{ env.SOURCE_BRANCH }} (informational only)..." - - # Get latest build workflow run on dev - BUILD_STATUS=$(gh run list --workflow="build-and-deploy.yml" \ - --branch ${{ env.SOURCE_BRANCH }} --limit 1 --json conclusion,status \ - --jq '.[0] | "\(.status):\(.conclusion)"' 2>/dev/null || echo "unknown:unknown") - - echo "Latest build status: $BUILD_STATUS" - - if [[ "$BUILD_STATUS" == *"failure"* ]]; then - echo "⚠️ WARNING: Latest ${{ env.SOURCE_BRANCH }} build failed" - echo "⚠️ Proceeding with merge anyway (bypass enabled)" - elif [[ "$BUILD_STATUS" == *"in_progress"* ]]; then - echo "ℹ️ Build is still running on ${{ env.SOURCE_BRANCH }}" - echo "ℹ️ Proceeding with merge anyway (bypass enabled)" - else - echo "✅ Latest build status: $BUILD_STATUS" + echo "⏳ Waiting for 'build-and-deploy.yml' workflow to complete on ${{ env.SOURCE_BRANCH }}..." + + MAX_WAIT=1800 # 30 minutes max wait + WAIT_INTERVAL=30 # Check every 30 seconds + ELAPSED=0 + + while [ $ELAPSED -lt $MAX_WAIT ]; do + # Get latest build workflow run on dev + BUILD_RUN=$(gh run list --workflow="build-and-deploy.yml" \ + --branch ${{ env.SOURCE_BRANCH }} --limit 1 --json status,conclusion,databaseId \ + --jq '.[0]' 2>/dev/null || echo '{}') + + STATUS=$(echo "$BUILD_RUN" | jq -r '.status // "unknown"') + CONCLUSION=$(echo "$BUILD_RUN" | jq -r '.conclusion // "none"') + RUN_ID=$(echo "$BUILD_RUN" | jq -r '.databaseId // "none"') + + echo "Build status: $STATUS | Conclusion: $CONCLUSION | Run ID: $RUN_ID" + + # Check if build completed + if [ "$STATUS" = "completed" ]; then + if [ "$CONCLUSION" = "success" ]; then + echo "✅ Build workflow completed successfully!" + break + elif [ "$CONCLUSION" = "failure" ]; then + echo "❌ Build workflow failed on ${{ env.SOURCE_BRANCH }}" + echo "⚠️ Cannot sync to main - build must pass first" + exit 1 + elif [ "$CONCLUSION" = "cancelled" ]; then + echo "⚠️ Build workflow was cancelled" + echo "⚠️ Cannot sync to main - build must complete successfully" + exit 1 + else + echo "⚠️ Build completed with conclusion: $CONCLUSION" + exit 1 + fi + fi + + # Build still in progress + echo "⏳ Build still running... waiting ${WAIT_INTERVAL}s (${ELAPSED}s elapsed)" + sleep $WAIT_INTERVAL + ELAPSED=$((ELAPSED + WAIT_INTERVAL)) + done + + if [ $ELAPSED -ge $MAX_WAIT ]; then + echo "❌ Timeout: Build did not complete within ${MAX_WAIT}s" + exit 1 fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7446a6d6eae61237173217152617cbf6bb92d138 Mon Sep 17 00:00:00 2001 From: krzysztofstaszalek Date: Thu, 20 Nov 2025 18:15:11 +0100 Subject: [PATCH 4/7] Updated changes from 5942-5943 releases --- docs/endpointprotector/admin/agent.md | 3 + .../admin/cap_module/deeppacket.md | 71 ++++-- .../admin/cap_module/dpilinux.webp | Bin 0 -> 8052 bytes .../admin/cap_module/dpimacosvpnoff.webp | Bin 0 -> 9976 bytes .../admin/cap_module/dpimacosvpnon.webp | Bin 0 -> 10814 bytes .../admin/cap_module/dpiports.webp | Bin 13468 -> 16994 bytes .../admin/cap_module/dpiwinregular.webp | Bin 0 -> 10528 bytes .../admin/cap_module/dpiwinstealth.webp | Bin 0 -> 9896 bytes .../msaddincentraladdinfileselect.webp | Bin 0 -> 17494 bytes .../cap_module/msaddincustomappselect.webp | Bin 0 -> 19922 bytes .../cap_module/msaddinspecifictargetuser.webp | Bin 0 -> 6972 bytes .../cap_module/msadminaddindeploywebp.webp | Bin 0 -> 19092 bytes .../admin/cap_module/mscustomaddin.webp | Bin 0 -> 8464 bytes .../cap_module/msnewoutlookaddintoolbar.webp | Bin 0 -> 23200 bytes .../admin/cap_module/newoutlook.md | 211 ++++++++++++++++++ .../admin/cap_module/webmailjson.webp | Bin 25726 -> 7516 bytes .../admin/dc_module/easylocksettings.webp | Bin 16220 -> 6208 bytes .../admin/dc_module/eeromode.webp | Bin 0 -> 6924 bytes .../admin/dc_module/globalrights.md | 3 + .../admin/dc_module/globalsettings.md | 26 ++- .../admin/ee_module/eemodule.md | 24 +- .../admin/ee_module/eeromode.webp | Bin 0 -> 119050 bytes .../serverdisplayname.webp | Bin 12718 -> 78318 bytes .../systemconfiguration/systemsettings.md | 14 +- docs/endpointprotector/requirements/client.md | 121 +++++----- .../requirements/components.md | 3 +- 26 files changed, 375 insertions(+), 101 deletions(-) create mode 100644 docs/endpointprotector/admin/cap_module/dpilinux.webp create mode 100644 docs/endpointprotector/admin/cap_module/dpimacosvpnoff.webp create mode 100644 docs/endpointprotector/admin/cap_module/dpimacosvpnon.webp create mode 100644 docs/endpointprotector/admin/cap_module/dpiwinregular.webp create mode 100644 docs/endpointprotector/admin/cap_module/dpiwinstealth.webp create mode 100644 docs/endpointprotector/admin/cap_module/msaddincentraladdinfileselect.webp create mode 100644 docs/endpointprotector/admin/cap_module/msaddincustomappselect.webp create mode 100644 docs/endpointprotector/admin/cap_module/msaddinspecifictargetuser.webp create mode 100644 docs/endpointprotector/admin/cap_module/msadminaddindeploywebp.webp create mode 100644 docs/endpointprotector/admin/cap_module/mscustomaddin.webp create mode 100644 docs/endpointprotector/admin/cap_module/msnewoutlookaddintoolbar.webp create mode 100644 docs/endpointprotector/admin/cap_module/newoutlook.md create mode 100644 docs/endpointprotector/admin/dc_module/eeromode.webp create mode 100644 docs/endpointprotector/admin/ee_module/eeromode.webp diff --git a/docs/endpointprotector/admin/agent.md b/docs/endpointprotector/admin/agent.md index 2e062d0c40..fb5bc5eece 100644 --- a/docs/endpointprotector/admin/agent.md +++ b/docs/endpointprotector/admin/agent.md @@ -26,6 +26,9 @@ Global Settings page, is known as the Tamper Mode setting. It is designed to pre termination or modification of the Endpoint Protector Agent. ::: +:::note +When enabling Debug logging, deploying a fresh installation, or during upgrade processes where critical drivers/services (such as DPI, browser plugins, or Outlook add-ins) must be reloaded, it is recommended to restart the operating system. This mandatory first step in troubleshooting ensures that all dependencies are properly initialized." +::: ## Agent Installation diff --git a/docs/endpointprotector/admin/cap_module/deeppacket.md b/docs/endpointprotector/admin/cap_module/deeppacket.md index a4edb23ad2..9a88a3845e 100644 --- a/docs/endpointprotector/admin/cap_module/deeppacket.md +++ b/docs/endpointprotector/admin/cap_module/deeppacket.md @@ -30,6 +30,45 @@ To ensure consistent DPI behavior after enabling or disabling the feature or upg the Endpoint Protector, a restart of your computer is required. ::: +## Stealthy DPI vs. regular DPI + +What are the different network visibility strategies available on Windows? + +- Stealthy DPI: Taps into a newly established network flow, where the content is extracted, decrypted, analyzed, encrypted, and then reintroduced. This method creates a direct network flow between the original application and the internet, without intermediaries. +- Regular DPI (Redirect-Based): Redirects network traffic to a transparent proxy server on localhost before it reaches the internet. This approach results in observable traffic directed to the localhost proxy on the local computer. + +How do Stealthy DPI and Redirect-Based DPI compare in terms of EPP Client functionality? + +- Stealthy DPI and Redirect-Based DPI are functionally similar and require no changes to Endpoint Protector policies. Users can select the mode that best suits their infrastructure preferences. Both methods use the same resources and generate identical events. +- However, they differ in handling bypasses for failed connections: + - Regular DPI (Redirect-Based): Offers more flexibility by allowing a feature to bypass connections that cannot be intercepted, with the proxy rebuilding the network connection to the destination after a failure. + - Stealthy DPI: Achieves a similar bypass result using the improved "DPI Bypass" feature available in Endpoint Protector version 5.9.3.0. + +When should you choose Stealthy DPI over Regular DPI (Redirect-Based)? + +- Third-Party DLP or Firewall Software: If third-party software has trouble handling or blocking network traffic originating from a local proxy, switching to Stealthy DPI is recommended. +- Security-Enhanced Applications: If certain applications experience connectivity issues with Regular DPI (Redirect-Based), opting for Stealthy DPI can resolve these issues. + +## Deep Packet Inspection Diagrams + +The diagrams below illustrate the high-level logic for Deep Packet Inspection (DPI) across different operating systems. Additionally, they illustrate the distinctions between Stealthy and Regular DPI modes of operation for macOS. + +### For Windows +- regular DPI +![Deep Packet Inspection on Windows - regular DPI](dpiwinregular.webp) + +- Stealthy DPI: +![Deep Packet Inspection on Windows - Stealth DPI](dpiwinstealth.webp) + +### For MacOS +- intercept VPN off: +![Deep Packet Inspection on Windows - intercept VPN off](dpimacosvpnoff.webp) + +- intercept VPN on: +![Deep Packet Inspection on Windows - intercept VPN on](dpimacosvpnon.webp) + +### For Linux +![Deep Packet Inspection on Linux](dpilinux.webp) ## Deep Packet Inspection Certificate @@ -58,9 +97,14 @@ generated. Issuing the Deep Packet Inspection Certificate on Windows is handled automatically and transparently by the Endpoint Protector Client. No additional steps are required. ::: +![Configuring the Deep Packet Inspection - Auto-refresh Certificate feature](autorefreshcert.webp) +EPP DPI module generates a certificate only at the first time a user visits a website and caches that certificate for subsequent visits to the same website. The certificate cache deletion interval can be configured in EPP Server versions 5.8.0.0 and above (please refer to this UM section [System Settings - DPI certificate](/docs/endpointprotector/admin/systemconfiguration/systemsettings) . Alternatively, the certificate cache is cleared either upon computer reboot or when the DPI feature is disabled. + +Endpoint Protector employs the same criteria as the Chromium open-source web browser for verifying website certificates, referencing the corporate CA certificates found in the system certificate stores. You can assess this validation by using diagnostic websites like https://badssl.com/. + +If needed, this feature can be configured through the DPI Bypass option described here [Global Settings - DPI configuration](/docs/endpointprotector/admin/dc_module/globalsettings#dpi-configuration). -![Configuring the Deep Packet Inspection - Auto-refresh Certificate feature](autorefreshcert.webp) ## Deep Packet Inspection Certificate on macOS @@ -184,6 +228,10 @@ Protection Policy. ![Deep Packet Inspection Ports and Settings](dpiports.webp) +:::note +The "Local" flag setting will only function with "Stealthy DPI" on Windows and "Intercept VPN Traffic" on macOS. It is not operational on Linux. +::: + In this section you can also manage the following settings: - Text Inspection - enable this setting to monitor confidential content typed in Teams, Skype, Slack, @@ -215,8 +263,7 @@ In this section you can also manage the following settings: ::: -- Block unsupported protocols in New Outlook – Enable this setting to block the send email - functionality in the New Outlook without interacting with the Outlook legacy functionality. +- Block unsupported protocols in New Outlook – Enable this setting to block unsupported protocols and the send email function in New Outlook without affecting legacy Outlook. Recommended for those not using the EPP add-in to limit the app as an egress channel. Keep off if EPP add-in is used. - Monitor webmail – Enable this setting to scan the subject and body for Gmail, Outlook and Yahoo on the browser. Attachments will be monitored regardless of this setting. @@ -255,6 +302,9 @@ In this section you can also manage the following settings: ![Allowed domains for Google Business accounts](alloweddomainsgoogle.webp) + :::warning + "To include consumer Google Accounts, such as those ending in @gmail.com and @googlemail.com, enter "consumer_accounts" in the list instead of "gmail.com". This change is necessary, and the current issue is being closed as "won't fix". We may consider opening a documentation task to link the relevant Google document to our user manual. For more information, refer to: [Google Support](https://support.google.com/a/answer/1668854?hl=en). + ::: ### Monitor Webmail JSON Format Parser Usage @@ -301,21 +351,6 @@ apply any changes in the JSON parser, unless Monitor Webmail is not working ::: -### Note on Peer Certificate Validation Usage - -If Deep Packet Inspection is ON and Peer Certificate Validation is enabled then you cannot access -unsecured websites and a certificate warning message is displayed. - -If Deep Packet Inspection is ON and Peer Certificate Validation is disabled then you can access -unsecured websites and no certificate warning messages are displayed. - -For Example; your organization uses an SSL inspection proxy or gateway. The certificates injected by -the proxy or gateway cannot be validated on the endpoint because they are either invalid or the -issuer CA certificate is not installed in the "Trusted Root Certification Authorities" in the computer -certificate store. To allow Deep Packet Inspection to work in this case you must skip peer -certificates validation. Endpoint Protector Client assumes that in this case the peer certificate -validation is performed by the proxy or gateway so that security is not compromised. - ## Deep Packet Inspection Applications From this section, you can enable or disable the Deep Packet Inspection functionality for each diff --git a/docs/endpointprotector/admin/cap_module/dpilinux.webp b/docs/endpointprotector/admin/cap_module/dpilinux.webp new file mode 100644 index 0000000000000000000000000000000000000000..65f9cb1c31eac527b0b216fce44434a29f5ec330 GIT binary patch literal 8052 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 literal 0 HcmV?d00001 diff --git a/docs/endpointprotector/admin/cap_module/dpimacosvpnoff.webp b/docs/endpointprotector/admin/cap_module/dpimacosvpnoff.webp new file mode 100644 index 0000000000000000000000000000000000000000..bab03f539bece30effa7ec589c4d397ac03fc0f1 GIT binary patch literal 9976 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