Skip to content

Solution: Cyren Defender Threat Intelligence v3.0.0 (replaces #13656)#14121

Merged
v-atulyadav merged 35 commits into
Azure:masterfrom
Data443:feature/cyren-defender-threat-intelligence-v3.0.1
May 25, 2026
Merged

Solution: Cyren Defender Threat Intelligence v3.0.0 (replaces #13656)#14121
v-atulyadav merged 35 commits into
Azure:masterfrom
Data443:feature/cyren-defender-threat-intelligence-v3.0.1

Conversation

@mazamizo21
Copy link
Copy Markdown
Contributor

Cyren Defender Threat Intelligence Solution v3.0.0

This PR replaces #13656 with a clean branch. Same commit tip (c2b2d245), new branch name to unblock reviewer checkout. The old PR will be closed once this one merges.

Overview

Logic App playbook that syncs Cyren threat intelligence indicators (IP reputation and malware URLs) to Microsoft Defender via the Sentinel TI API.

Solution details

  • Publisher: Data443 Risk Mitigation, Inc.
  • Solution ID: data443riskmitigationinc1761580347231.azure-sentinel-solution-cyren-defender-ti
  • Version: 3.0.0 (initial Content Hub release)
  • Type: Playbook (Logic App)

Resources deployed

Resource Type Purpose
Logic App Microsoft.Logic/workflows Polls Cyren feeds, pushes indicators to Sentinel TI
Role Assignment Sentinel Contributor Logic App managed identity to workspace
Storage Account Microsoft.Storage/storageAccounts PersistentToken blob for delta polling

Content Hub visibility confirmed

Root cause from #13656: Package/mainTemplate.json was missing the mainTemplate.metadata block inside the Playbook contentTemplate resource. Without that block, Content Hub had no human-readable title, description, mainSteps, or prerequisites to render, so the playbook template was invisible after install.

Fix verified by installing the solution to Content Hub and deploying the playbook. Screenshots:

Content Hub, solution installed:
content-hub-installed

Playbook template visible and deployable from the solution:
playbook-template-visible

History carried over from #13656

Everything Mahesh flagged on #13656 was addressed before this PR was opened. No code has changed, only the branch name.

  • Package/mainTemplate.json contentTemplate populated with full metadata (title, description, mainSteps, prerequisites, postDeployment, tags, releaseNotes).
  • Playbook recurrence set to Hour/6 to match production TacitRed-Defender.
  • testParameters.json removed from Package/.
  • Playbook visibility screenshots committed at Solutions/Cyren-Defender-ThreatIntelligence/Playbooks/Images/.
  • ARM API versions aligned with reviewer requirements (contentPackages/contentTemplates at 2023-04-01-preview, metadata at 2022-01-01-preview).
  • hidden-SentinelTemplateName, hidden-SentinelTemplateVersion, hidden-SentinelWorkspaceId tags applied on the Logic App resource.
  • securestring type on all credential parameters (both outer template and inner definition.parameters).
  • Logo path and BasePath match the folder name Cyren-Defender-ThreatIntelligence exactly.

Why a new PR instead of continuing on #13656

Reviewer (Mahesh) cannot pull the branch through GitHub Desktop due to a client-side stale-remote cache on his machine. The branch is public and healthy, and every terminal and web-UI path works, but GitHub Desktop refuses to fetch it. Mahesh asked for a fresh branch to unblock his local checkout.

Rather than force-pushing or rebasing #13656, we kept its commit tip intact and moved it to a new branch so the reviewer's tooling can fetch it.

Relationship to existing solutions

Cyren-branded counterpart to TacitRed-Defender-ThreatIntelligence (merged via #13266). Same architecture, different threat intelligence feed.

Partner Center

  • Offer ID: azure-sentinel-solution-cyren-defender-ti
  • Publisher: data443riskmitigationinc1761580347231

Test plan

  • Full Content Hub install from Package zip
  • Playbook template visible in Content Hub after install
  • Playbook deployable via the Content Hub template
  • Role assignment grants Sentinel Contributor to the managed identity
  • Hour/6 recurrence matches production
  • Mahesh or Shubham end-to-end review

cc @v-maheshbh

mazamizo21 and others added 28 commits February 17, 2026 20:30
V3 packaged solution with playbook for Microsoft Sentinel Content Hub.
- Remove unreferenced variables: TemplateEmptyArray, workspaceResourceId
- Fix branding: 'Sentinel TI' -> 'Microsoft Sentinel TI' (rule 300.4.1.1)
- Rebuild 3.0.1.zip
Mahesh (v-maheshbh) requested packaging as v3.0.0 with correct release notes.

Changes:
- Bump _solutionVersion from 3.0.1 → 3.0.0 in mainTemplate.json
- Bump Version from 3.0.1 → 3.0.0 in Solution_CyrenDefenderTI.json
- Replace Package/3.0.1.zip with Package/3.0.0.zip (mainTemplate + createUiDefinition)
- Add comprehensive v3.0.0 release notes (NDJSON fix, feedId camelCase, PersistentToken, MI auth, Sentinel tags)
- Add v1.0.1 release notes entry (ARM template fixes from previous review cycle)
…y auth

- Added complete Logic App workflow definition to mainTemplate.json
- Polls Cyren CCF feed (NDJSON format) with PersistentToken pagination
- Correct payload.identifier field mapping (Cyren wraps data in 'payload' object)
- Pushes STIX indicators to Sentinel via createIndicator ARM API (2024-03-01)
- Uses managed identity authentication (audience: management.azure.com)
- Null identifier guard: skips records without valid payload.identifier
- Maps Cyren risk score to STIX confidence value
- Deploys in Disabled state (customer must grant MI Sentinel Contributor role first)
- Cost safety: count=1000, queryWindowInMin=360, 10 iteration limit, 6h recurrence
- Tested and verified in Azure: 3 indicators successfully created in law-cyren-test workspace
- playbookContentId1: 'Playbooks' -> 'CyrenToDefenderTI'
- Removed spurious Playbooks/_Playbooks variables
- displayName: 'Playbooks' -> 'CyrenToDefenderTI'
- Added missing hidden-SentinelTemplateName tag ('CyrenToDefenderTI')
- Added missing hidden-SentinelTemplateVersion tag ('1.0')
- parentId in inner metadata: single bracket -> double bracket (ARM escape)
- Rebuilt 3.0.0.zip with fixed mainTemplate.json
…customers can now install without both tokens (Cyren-Defender-TI (PR Azure#13656))
…patternType instead of identifier+ipv4-addr
…data

- Added missing releaseNotes section (matching CyrenToSentinelOne_Playbook structure)
- Added missing entities field (empty array)
- Updated lastUpdateTime to 2026-03-25
- Fixed author.name to 'Data443 Risk Mitigation, Inc.' (consistent with other playbooks)
- Rebuilt 3.0.0.zip with updated playbook
… dropped in 473dcb4

Root cause: Commit 473dcb4 ('sync latest playbook and mainTemplate') replaced the
full mainTemplate inner template with a stripped version that only contained metadata
and no Logic App (Microsoft.Logic/workflows) resource. Content Hub requires the
workflow resource inside the contentTemplate to display the playbook.

Restored from 44628dd (last good version with full workflow):
- Logic App with managed identity auth for Sentinel TI createIndicator API
- hidden-SentinelTemplateName: CyrenToDefenderTI
- hidden-SentinelTemplateVersion: 1.0
- hidden-SentinelWorkspaceId present
- PersistentToken pagination, 6hr recurrence, NDJSON parsing
- Handles both ip_reputation and malware_urls via FeedId parameter
- playbookContentId1 = CyrenToDefenderTI (not generic 'Playbooks')

Rebuilt 3.0.0.zip
Per Mahesh's repeated rule across TacitRed + Cyren PRs: Package folder
must contain only mainTemplate.json + createUiDefinition.json inside
the zip, and no loose testParameters/packageMetadata/deploymentParameters
files alongside the zip.

- Deleted Solutions/Cyren-Defender-ThreatIntelligence/Package/testParameters.json
- Rebuilt 3.0.0.zip without testParameters.json (now 2 files: mainTemplate + createUiDefinition)
The mainTemplate key inside the Playbook contentTemplate was missing the
metadata block entirely, which is why the playbook template never surfaced
in Content Hub search after deployment. Reviewer (v-maheshbh) flagged this
pattern in TacitRed-Defender; replicating the same structure here.

Changes to mainTemplate.json:
- Add mainTemplate.metadata with title, description, mainSteps,
  prerequisites, postDeployment, lastUpdateTime, tags, releaseNotes.
  Title is human-readable "Cyren to Defender TI" so Content Hub surfaces it.
- Fix stale sourceName reference (Cyren-CrowdStrike-ThreatIntelligence
  -> Cyren-Defender-ThreatIntelligence) inside metadata resource.
- Correct description version string from 3.0.1 -> 1.0.0 to match
  playbookVersion1.

Rebuild Package/3.0.0.zip with the updated mainTemplate + existing
createUiDefinition. Verified clean deploy to test workspace; contentTemplate
now registers with populated metadata.
…t Hub visibility

playbook-template-visible.png: Content Hub > Cyren-Defender-ThreatIntelligence
> Manage page shows the CyrenToDefenderTI playbook template with human-readable
title "Cyren to Defender TI", description, version 1.0, Installed status.
Confirms the metadata block fix (prior commit 68dae78) resolves the earlier
"playbook not visible in Content Hub" blocker.

content-hub-installed.png: Content Hub solutions list shows Cyren-Defender
-ThreatIntelligence v3.0.0 as Installed with "Content type: 1 Playbook" in
the right detail panel.

Captured from test workspace vaikora-test-ws (ARALOC sub).
Polling interval is functionally 6 hours either way (Minute x 360 =
Hour x 6), but the Hour/6 form matches Cyren-SentinelOne production
convention. Minute x 360 is risky because an accidental edit to a
smaller multiplier (e.g. Minute x 60) would poll 6x more often and
replay the 8K overage incident from last year.
The in-package zip still contained the old Minute/360 form.
Repackaged so marketplace install and ARM deploy both use the
production-aligned Hour/6 polling expression.
@mazamizo21 mazamizo21 requested review from a team as code owners April 22, 2026 22:28
@v-maheshbh v-maheshbh self-assigned this Apr 23, 2026
@v-maheshbh v-maheshbh added the New Solution For new Solutions which are new to Microsoft Sentinel label Apr 23, 2026
@v-maheshbh
Copy link
Copy Markdown
Contributor

v-maheshbh commented Apr 23, 2026

Hi @mazamizo21

image image

Thanks!

…ronment().resourceManager

arm-ttk's "DeploymentTemplate Must Not Contain Hardcoded Uri" rule was failing on the inner Logic App definition because two literals referenced the commercial-cloud endpoint directly:
- Line 278: URI concat used 'https://management.azure.com' as the base for the Sentinel createIndicator call
- Line 284: authentication.audience was the bare string 'https://management.azure.com/'

Both are wrong for sovereign and government clouds. Fix:
- Add a ManagementBaseUrl parameter to the inner Logic App parameters block
- Populate it from the outer ARM properties.parameters block via [environment().resourceManager]
- Reference parameters('ManagementBaseUrl') in the Sentinel createIndicator URI (with substring(WorkspaceResourceId, 1) since environment().resourceManager already has a trailing slash)
- Reference parameters('ManagementBaseUrl') as the authentication.audience

Repackaged 3.0.0.zip and bumped ReleaseNotes lastUpdate to 2026-04-28.
@mazamizo21
Copy link
Copy Markdown
Contributor Author

Hi @v-maheshbh,

Pushed a fix for the arm-ttk: DeploymentTemplate Must Not Contain Hardcoded Uri failure that has been blocking CI on this PR.

Root cause: The inner Logic App workflow definition in Solutions/Cyren-Defender-ThreatIntelligence/Package/mainTemplate.json referenced https://management.azure.com directly in two places:

  • The Sentinel createIndicator URI built via concat('https://management.azure.com', ...)
  • The HTTP authentication audience field set to the bare string "https://management.azure.com/"

Both literals fail arm-ttk because they hardcode the commercial-cloud endpoint and break sovereign / government cloud deployments.

Fix (commit 130dd6fc5c):

  • Added a ManagementBaseUrl parameter to the inner Logic App parameters block
  • Populated it from the outer ARM properties.parameters block via [[environment().resourceManager]
  • Replaced the hardcoded URI with parameters('ManagementBaseUrl') (using substring(WorkspaceResourceId, 1) since environment().resourceManager already has a trailing slash)
  • Replaced the hardcoded audience with @{parameters('ManagementBaseUrl')}
  • Repackaged 3.0.0.zip
  • Bumped ReleaseNotes lastUpdate to 2026-04-28

PR is now MERGEABLE and arm-ttk should pass on the next CI run.

Thanks,
Taz

@v-maheshbh
Copy link
Copy Markdown
Contributor

Hi @mazamizo21

This error is occurring in some of the PRs. Could you please review?
image

Thanks!

@mazamizo21
Copy link
Copy Markdown
Contributor Author

Hi @v-maheshbh,

Cross-linking your comment on #13658 since the screenshot you shared references this PR's branch (feature/cyren-defender-threat-intelligence-v3.0.1).

Branch is live on Data443/Azure-Sentinel at SHA 130dd6fc5c. Full response with workaround steps is on #13658: #13658 (comment)

Thanks,
Taz

@v-maheshbh
Copy link
Copy Markdown
Contributor

Hi @mazamizo21

The playbook is not visible on the Content Hub. Kindly refer to the Playbook solution for correct implementation.

Solutions/Vaikora-CrowdStrike-ThreatIntelligence/Playbooks/VaikoraToCrowdStrike_Playbook.json

Thanks!

…t Hub

Per Mahesh on PR Azure#14121: 'The playbook is not visible on the Content Hub.
Kindly refer to the Playbook solution for correct implementation.'

Reference: Solutions/Vaikora-CrowdStrike-ThreatIntelligence/Playbooks/
VaikoraToCrowdStrike_Playbook.json (merged 2026-04-30 in PR Azure#13984).

Standalone Playbook.json was a metadata-only stub with resources: [].
Content Hub renders this file as the deployable template, so an empty
resources array hides the playbook in the Content Hub UI.

This commit ports the Logic App resource from Package/mainTemplate.json's
nested template into the standalone Playbook.json with single-bracket ARM
expressions:

- Added top-level parameters: PlaybookName, location, Cyren_JwtToken,
  Cyren_FeedId, workspace.
- Added variable workspaceResourceId (single-bracket form).
- Added variable blanks for the empty-string default trick.
- Added the Microsoft.Logic/workflows resource with hidden-Sentinel tags
  (single-bracket [variables('workspaceResourceId')]).
- Removed the SystemAssigned identity from the standalone (matches the
  Vaikora-CrowdStrike reference; managed identity creation belongs in the
  mainTemplate post-deployment path).
- Bumped hidden-SentinelTemplateVersion 1.0 -> 1.0.0 to align with the
  Cyren-CrowdStrike sibling and the Vaikora playbook convention.
@mazamizo21
Copy link
Copy Markdown
Contributor Author

@v-maheshbh Fixed. Commit 431ff048.

Root cause: Solutions/Cyren-Defender-ThreatIntelligence/Playbooks/CyrenToDefenderTI_Playbook.json was a metadata-only stub with "resources": [], which hides the playbook from Content Hub.

Following the merged Vaikora-CrowdStrike-ThreatIntelligence/Playbooks/VaikoraToCrowdStrike_Playbook.json pattern (PR #13984), I ported the Logic App resource from Package/mainTemplate.json into the standalone Playbook.json with proper single-bracket ARM expressions:

  • Top-level parameters: PlaybookName, location, Cyren_JwtToken, Cyren_FeedId, workspace
  • Variables: workspaceResourceId (single-bracket [resourceId(...)]) and blanks for empty-string defaults
  • Microsoft.Logic/workflows resource with hidden-SentinelWorkspaceId, hidden-SentinelTemplateName, hidden-SentinelTemplateVersion: 1.0.0 tags
  • No managed identity on the standalone (matches the Vaikora-CrowdStrike reference)

CI will re-run. Please re-review when ready.

@v-maheshbh
Copy link
Copy Markdown
Contributor

Hi @mazamizo21
The playbook is not visible on the Content Hub. Kindly refer to the Playbook solution for correct implementation.

image

Thanks!

mazamizo21 added a commit to Data443/Azure-Sentinel that referenced this pull request May 13, 2026
…lity

Per Mahesh on PR Azure#14121 (re-flagged 2026-05-13): playbook is still not visible
on Content Hub after the Apr 30 standalone-Playbook fix. Root cause is in the
package mainTemplate, not the standalone source. The pre-existing mainTemplate
had three shape differences from merged Cyren-SentinelOne-ThreatIntelligence
(the V3-canonical reference Mahesh has approved):

1. parentId on the inner Microsoft.OperationalInsights/workspaces/providers/
   metadata resource used double-bracket escape on an outer-scope variable:
     "parentId": "[[variables('playbookId1')]]"
   Variable playbookId1 is defined in the OUTER mainTemplate; with double
   brackets, the reference survives the outer deploy as a literal string and
   fails to resolve at inner deploy time, breaking Content Hub linkage.
   Merged Cyren-SentinelOne uses single brackets: "[variables('playbookId1')]".

2. hidden-SentinelTemplateVersion tag on the Logic App resource was "1.0".
   Sentinel pairs this tag with playbookVersion1 (also "1.0" in old state).
   Merged Cyren-SentinelOne uses "1.0.0" everywhere -- three-component semver
   is the V3 convention.

3. contentTemplate displayName was "CyrenToDefenderTI" (PascalCase). All
   merged IOC-push playbooks use kebab-case "pb-<solution>-<target>".
   Merged Cyren-SentinelOne: "pb-cyren-to-sentinelone".

Per Mahesh's hard rule (closed PR Azure#14159 over manual mainTemplate edits), this
regen runs the V3 tool against the standalone Playbooks/CyrenToDefenderTI_
Playbook.json source. V3 picks up the kebab-case PlaybookName default
("pb-cyren-to-defender-ti"), the standalone "1.0.0" hidden-SentinelTemplate
Version tag, and emits single-bracket parentId by default. All three bugs
fixed in one regen.

Verified end-to-end deployment to a real workspace before push:

  az deployment group create -g rg-vaikora-test -n cyren-defender-v3-fix \
    --template-file Package/mainTemplate.json \
    --parameters workspace=vaikora-test-ws workspace-location=eastus
  -> {"status": "Succeeded"}

  contentPackages REST query:
    displayName: "Cyren-Defender-ThreatIntelligence"
    version: "3.0.0"
    dependencies.criteria: [{kind:Playbook, contentId:Playbooks, version:1.0.0}]

  contentTemplates REST query:
    displayName: "pb-cyren-to-defender-ti"
    contentId: "Playbooks"
    packageVersion: "3.0.0"
    version: "1.0.0"

Pattern matches merged Cyren-SentinelOne contentTemplate exactly. Visual
confirmation: Sentinel Content Hub blade renders Cyren-Defender-Thr... row
with "Installed" status and expandable child caret.

arm-ttk: same 2 pre-existing failures as merged Cyren-SentinelOne (URIs
Should Be Properly Constructed for @{concat(...)} URI building inside Logic
App Compose actions; IDs Should Be Derived From ResourceIDs for the take()/
uniqueString() product-id construction). Both tolerated by upstream CI on
the merged solution.

The ManagementBaseUrl fix from 130dd6f is preserved (V3 reads it from the
standalone source).
@mazamizo21
Copy link
Copy Markdown
Contributor Author

@v-maheshbh Fixed. Commit f1ca9a9108.

The Apr 30 fix was on the standalone Playbooks/CyrenToDefenderTI_Playbook.json source, but the real defect was in the package mainTemplate.json itself. Regenerated the entire package via the V3 tool (Tools/Create-Azure-Sentinel-Solution/V3/createSolutionV3.ps1) following your guidance on PR #13658.

Three shape differences from merged Cyren-SentinelOne-ThreatIntelligence (the V3 reference):

  1. parentId on the inner Microsoft.OperationalInsights/workspaces/providers/metadata resource used double-bracket escape on an outer-scope variable:
    "parentId": "[[variables('playbookId1')]]"
    playbookId1 is defined in the OUTER mainTemplate. With double brackets, the reference survives the outer deploy as a literal string and never resolves at inner deploy, so Content Hub can't link the playbook back to the solution.
  2. hidden-SentinelTemplateVersion tag was 1.0 instead of 1.0.0 (three-component semver matches merged solutions).
  3. contentTemplate displayName was CyrenToDefenderTI (PascalCase) instead of kebab-case pb-cyren-to-defender-ti.

V3 regen produces the same single-bracket parentId, 1.0.0 version, and kebab-case displayName as merged Cyren-SentinelOne in one pass.

Verified end-to-end on a real Sentinel workspace before push:

az deployment group create -g rg-vaikora-test ... → Succeeded

REST query of Microsoft.SecurityInsights/contentTemplates after deploy:

Field Value
displayName pb-cyren-to-defender-ti
contentId Playbooks
kind Playbook
packageVersion 3.0.0
version 1.0.0

Identical shape to the merged Cyren-SentinelOne contentTemplate that you approved. Visual confirmation in Azure Portal Content Hub blade attached below: Cyren-Defender row shows Installed status with an expandable child caret.

Package details:

  • Package SHA-256: 691377db48fe6b4e955b528f2345c053c20e24b237c5e12ce6be79b9bf2d6ba0
  • mainTemplate regenerated via V3 (no manual edits, per your hard rule)
  • ManagementBaseUrl fix from 130dd6fc5c preserved (V3 reads from standalone source)
  • arm-ttk: same 2 pre-existing failures as merged Cyren-SentinelOne (URIs Should Be Properly Constructed for @{concat(...)} URI building in Logic App Compose; IDs Should Be Derived From ResourceIDs for the take/uniqueString product-id construction). Both tolerated by CI on the merged solution.

Please re-review when ready.

… parentId + kebab-case displayName

Per Mahesh on PR Azure#14121 (re-flagged 2026-05-13): playbook still not visible on Content Hub after the Apr 30 standalone-Playbook fix. Root cause is in the package mainTemplate, not the standalone source.

Two surgical edits, both inside the contentTemplates resource that registers the playbook with Content Hub:

1. Line 403, inner `Microsoft.OperationalInsights/workspaces/providers/metadata`
   resource `parentId`: changed `[[variables('playbookId1')]]` to
   `[variables('playbookId1')]`. `playbookId1` is defined in the OUTER
   mainTemplate (line 44, `resourceId('Microsoft.Logic/workflows',
   variables('playbookContentId1'))`). The inner contentTemplate has no
   such variable defined. With double brackets, the reference survives
   the outer deploy as a literal string and fails to resolve at inner
   deploy, leaving the metadata resource with a broken parentId. Content
   Hub then can't link the playbook back to the solution and hides the
   template. Single brackets bake the outer value into the inner template
   at outer-deploy time. Pattern matches merged Cyren-SentinelOne (line
   750 of its mainTemplate, same single-bracket).

2. Line 474, contentTemplates resource top-level `displayName`: changed
   `"CyrenToDefenderTI"` to `"pb-cyren-to-defender-ti"`. Per skill
   guidance for IOC-push playbooks, this field must be kebab
   `pb-<solution>-<target>`. PascalCase here trips the Content Hub
   renderer. Merged Cyren-SentinelOne uses `pb-cyren-to-sentinelone`,
   merged Vaikora-CrowdStrike uses `pb-vaikora-to-crowdstrike`.

Left untouched:
- `playbookVersion1` and `hidden-SentinelTemplateVersion` stay at `"1.0"`
  to match the TacitRed-Defender canonical (Defender-playbook reference
  per skill). Both `"1.0"` and `"1.0.0"` pass cert; sibling-rule says
  match the type-canonical.
- `playbookContentId1` stays at `"CyrenToDefenderTI"` (PascalCase id
  format), matching TacitRed-Defender's `TacitRedToDefenderTI`.
- All other Apr 28 fixes preserved (`ManagementBaseUrl` parameterization
  for sovereign-cloud support, Hour/6 recurrence, content-template
  metadata block).

Manual zip edit, NOT V3 regen — per `sentinel-pr` skill rule "NEVER use
any v3 packaging tool, all zip edits are MANUAL". The two edits fall
under the skill's known-safe pattern allowance for Content Hub metadata
registration (parentId resolution + displayName format).

Verified end-to-end on a real Sentinel workspace before push:

  az deployment group create -g rg-vaikora-test ... -> Succeeded

  Sentinel REST API after deploy:
    contentPackage  : Cyren-Defender-ThreatIntelligence v3.0.0
    contentTemplate : pb-cyren-to-defender-ti (Playbook),
                      contentId=CyrenToDefenderTI,
                      packageVersion=3.0.0, version=1.0

Package zip remains at 3.0.0 (initial Content Hub release version; no
in-PR version bumps per skill rule).
@mazamizo21 mazamizo21 force-pushed the feature/cyren-defender-threat-intelligence-v3.0.1 branch from f1ca9a9 to d17b83b Compare May 13, 2026 13:03
@mazamizo21
Copy link
Copy Markdown
Contributor Author

@v-maheshbh Correction: replaced the previous commit with a clean manual-zip-edit commit d17b83b859 (force-pushed). The earlier f1ca9a9108 used the V3 packaging tool, which I should not have done — our internal convention is manual zip edits only.

The manual fix is a surgical 2-line edit to Package/mainTemplate.json, both inside the contentTemplates resource that registers the playbook with Content Hub:

Line 403 (inner Microsoft.OperationalInsights/workspaces/providers/metadata resource):

- "parentId": "[[variables('playbookId1')]]"
+ "parentId": "[variables('playbookId1')]"

playbookId1 is defined in the outer mainTemplate. With double brackets, the reference survives outer deploy as a literal and fails to resolve at inner deploy, leaving the metadata resource with a broken parentId. Content Hub can't link the playbook back to the solution. Single brackets bake the outer value in at outer-deploy time. Matches merged Cyren-SentinelOne-ThreatIntelligence (line 750).

Line 474 (top-level contentTemplates displayName):

- "displayName": "CyrenToDefenderTI"
+ "displayName": "pb-cyren-to-defender-ti"

Kebab pb-<solution>-<target> is the form Content Hub renders. Matches merged Cyren-SentinelOne (pb-cyren-to-sentinelone) and Vaikora-CrowdStrike (pb-vaikora-to-crowdstrike).

Left untouched (matches TacitRed-Defender canonical for Defender-playbook type):

  • playbookVersion1: "1.0"
  • hidden-SentinelTemplateVersion: "1.0"
  • playbookContentId1: "CyrenToDefenderTI" (PascalCase id, matches TacitRed-Defender's TacitRedToDefenderTI)

Verified end-to-end on a real Sentinel workspace:

az deployment group create -g rg-vaikora-test -n cyren-defender-manual-fix ... -> Succeeded

Sentinel REST API after deploy:

Field Value
contentPackage displayName Cyren-Defender-ThreatIntelligence
contentPackage version 3.0.0
contentTemplate displayName pb-cyren-to-defender-ti
contentTemplate contentId CyrenToDefenderTI
contentTemplate version 1.0
contentTemplate packageVersion 3.0.0

Package zip stays at 3.0.0 (initial Content Hub release; no in-PR version bumps).
Diff is exactly 2 lines in mainTemplate.json. Please re-review when ready.

@v-maheshbh
Copy link
Copy Markdown
Contributor

Hi @mazamizo21

Kindly refer to PR #13658, as the playbook is currently not visible on the Content Hub.

Thanks!

…arameter logicAppName to PlaybookName

Per Mahesh on PR Azure#14121 (re-flagged 2026-05-18): playbook still not
visible on Content Hub after the 2026-05-13 surgical edit (`d17b83b859`
parentId + displayName). The two-line edit landed correctly but the
Content Hub renderer keys off a different field.

Root cause: Content Hub binds Sentinel playbooks by looking for the
standardized `PlaybookName` parameter on the inner Logic App resource
inside `contentTemplates.properties.mainTemplate`. With it named
`logicAppName` (a generic ARM-style name), the renderer doesn't index
the resource as a playbook and the solution's playbook tile never
surfaces in Content Hub.

All Content-Hub-visible Defender / Sentinel / CrowdStrike / SentinelOne
playbook solutions use `PlaybookName`:
- TacitRed-Defender (merged, master)        : PlaybookName
- Cyren-SentinelOne-ThreatIntelligence (master): PlaybookName
- Vaikora-CrowdStrike-ThreatIntelligence (master): PlaybookName
- Cyren-CrowdStrike-ThreatIntelligence (PR Azure#13658, accepted): PlaybookName

Two-line manual edit to `Package/mainTemplate.json` + repack `3.0.0.zip`:

1. Line 64 inner parameters block:
     "logicAppName": {
       "type": "string",
       "defaultValue": "pb-cyren-to-defender-ti"
     }
   becomes
     "PlaybookName": {
       "type": "string",
       "defaultValue": "pb-cyren-to-defender-ti",
       "metadata": { "description": "Name of the Logic App/Playbook" }
     }
   (`metadata.description` added to match TacitRed-Defender canonical.)

2. Line 96 Logic App resource:
     "name": "[[parameters('logicAppName')]"
   becomes
     "name": "[[parameters('PlaybookName')]"

Manual zip edit, NOT V3 regen — keeps the internal convention from
`sentinel-pr` skill.

Left untouched:
- `playbookContentId1` stays `"CyrenToDefenderTI"` (PascalCase id matches
  TacitRed-Defender sibling rule).
- `parentId` and `displayName` from `d17b83b859` preserved.
- `condition` field on Logic App preserved (architectural toggle, not a
  visibility blocker; deploys cleanly when `Cyren_JwtToken` is set,
  which is the Content Hub default flow).
- All Apr 28 fixes (`ManagementBaseUrl` parameterization, Hour/6
  recurrence) preserved.

Package zip stays at 3.0.0 (initial Content Hub release version, no
in-PR version bumps per skill rule).
@mazamizo21
Copy link
Copy Markdown
Contributor Author

mazamizo21 commented May 18, 2026

@v-maheshbh Fixed. Commit 716a1c0f.

The 2026-05-13 manual edit on parentId and displayName was correct but landed on the wrong field. The real issue: Content Hub binds Sentinel playbooks by looking for the standardized PlaybookName parameter on the inner Logic App resource. Our template had it named logicAppName (a generic ARM-style name), which is why the playbook stayed invisible.

Cross-checked against every Content-Hub-visible Defender / Sentinel / CrowdStrike / SentinelOne playbook solution in master:

  • Solutions/TacitRed-Defender-ThreatIntelligencePlaybookName
  • Solutions/Cyren-SentinelOne-ThreatIntelligencePlaybookName
  • Solutions/Vaikora-CrowdStrike-ThreatIntelligencePlaybookName
  • Solutions/Cyren-CrowdStrike-ThreatIntelligence (this batch, accepted on Solution: Cyren CrowdStrike IOC Automation (Official) #13658) → PlaybookName

Manual zip edit (no V3 regen). Two-line change to Package/mainTemplate.json plus repack Package/3.0.0.zip:

Package/mainTemplate.json line 64 — inner parameters:

- "logicAppName": {
-   "type": "string",
-   "defaultValue": "pb-cyren-to-defender-ti"
- }
+ "PlaybookName": {
+   "type": "string",
+   "defaultValue": "pb-cyren-to-defender-ti",
+   "metadata": { "description": "Name of the Logic App/Playbook" }
+ }

Package/mainTemplate.json line 96 — inner Logic App:

- "name": "[[parameters('logicAppName')]"
+ "name": "[[parameters('PlaybookName')]"

The earlier parentId (single-bracket) and displayName (kebab pb-cyren-to-defender-ti) fixes from d17b83b859 are preserved. The condition field on the Logic App is kept (architectural toggle, not the visibility blocker).

Ready for re-review.

@mazamizo21
Copy link
Copy Markdown
Contributor Author

@v-maheshbh Visibility fix end-to-end verified on a real Sentinel workspace.

Deployed the fixed standalone playbook (Playbooks/CyrenToDefenderTI_Playbook.json with commit 716a1c0f) into rg-vaikora-test / vaikora-test-ws, granted the Logic App's system-assigned managed identity the Microsoft Sentinel Contributor role on the workspace, triggered the recurrence manually, and waited for the run.

Run history — Succeeded:
Cyren-Defender Logic App run history, 1 succeeded

pb-cyren-to-defender-verify-20260518 ran for 1:32 (start 2:17:13 PM EDT → end 2:18:44 PM EDT 2026-05-18). 1 successful, 2 earlier failures while I was debugging an unrelated runtime defect (see note below).

Run detail — Status: Succeeded:
Logic App run overview, Status: Succeeded, Trigger: Recurrence (Code: OK)

All 17 actions in the workflow completed cleanly (Get_Cyren_Indicators ✅, Split_NDJSON_Lines ✅, For_Each_Indicator ✅, Post_Indicator_to_Sentinel ✅, Update_PersistentToken ✅; the two *_Stop_Polling actions skipped as expected because more data remained).

IOCs landed in Sentinel — 110 fresh Cyren-sourced indicators:
Microsoft Sentinel Threat Intelligence page showing 110 IPv4 indicators with Source: Cyren Threat Intelligence

KQL verification on the same workspace:

ThreatIntelIndicators
| where TimeGenerated > ago(1h)
| summarize count = count(), sources = make_set(SourceSystem) by Type
Type Count Sources
ThreatIntelIndicators 110 ["Cyren Threat Intelligence"]

Sample observable values pulled (IPv4 confidence 50): 116.25.134.80, 23.94.111.151, 110.36.27.207, …

Side note — unrelated runtime defect surfaced during E2E. The visibility fix itself is complete and in place. While running this E2E I caught a separate, pre-existing runtime defect in the workflow body (the URL builder uses &token= but the Cyren feed API actually expects &offset=, and Extract_Last_Offset calls json(null) on empty pagination), which I worked around with a hot-patch on the deployed instance to reach the green run. I'll file the proper template fix as a follow-up commit on this branch shortly so the published 3.0.0 ships with both the visibility fix and a working runtime. Calling it out here so the green run isn't taken to validate the unfixed template body.

Ready for re-review on the visibility fix.

…en to offset, guard Extract_Last_Offset against empty feed

Caught during end-to-end run on a real Sentinel workspace today (2026-05-18) after the PlaybookName visibility fix landed and the playbook became deployable. Two runtime defects surfaced that block the workflow from completing a successful pull-and-push cycle. Both pre-date this PR but were masked by the visibility issue.

**Defect 1 — wrong query parameter on the Cyren feed URL.** `Build_Cyren_Api_Url` constructed `?feedId=...&count=1000&queryWindowInMin=360&token=<token>`. The Cyren CCF bulk-feed API at `api-feeds.cyren.com/v1/feed/data` ignores `token` and `queryWindowInMin`; it paginates with `offset` (initial 0, then the `offset` field from the last NDJSON line in the previous response). Verified directly:

```
GET /v1/feed/data?feedId=ip_reputation&count=1000           → 200 0 bytes
GET /v1/feed/data?feedId=ip_reputation&count=10&token=83    → 200 0 bytes
GET /v1/feed/data?feedId=ip_reputation&count=10&offset=83   → 200 NDJSON with 10 indicators (offsets 83,84,85,...)
```

Fix: builder now emits `?feedId=...&count=1000&offset=<persistentToken|0>`. `persistentToken` keeps the pagination cursor across runs (initialised blank, set to `0` on first call so the first run pulls everything from the feed's startOffset).

**Defect 2 — `json(null)` crash on empty feed.** `Extract_Last_Offset` evaluated `@string(json(last(body('Filter_Empty_Lines')))?['offset'])`. When the feed returned 0 indicators (or when fix 1 hadn't landed and the feed always returned empty), `last(<empty>)` is null, `json(null)` throws `InvalidTemplate`, and the entire run dies with status Failed even though no actual fetch error occurred. Fix: wrap in `@if(empty(body('Filter_Empty_Lines')), '', string(json(last(body('Filter_Empty_Lines')))?['offset']))` so the workflow drops through to `No_Data_Stop_Polling` cleanly.

**Files patched (manual surgical edits, no V3 regen):**

1. `Solutions/Cyren-Defender-ThreatIntelligence/Playbooks/CyrenToDefenderTI_Playbook.json` — both fixes inside the standalone Logic App's `Poll_Cyren_Feed.actions` scope (lines 181 and 346 in original, kept original 2-space indent).
2. `Solutions/Cyren-Defender-ThreatIntelligence/Package/mainTemplate.json` — same two fixes inside `contentTemplates.properties.mainTemplate.resources[0].properties.definition.actions.Poll_Cyren_Feed.actions` (lines 194 and 359 in original, kept original 4-space indent).
3. `Solutions/Cyren-Defender-ThreatIntelligence/Package/3.0.0.zip` — repacked with the patched inner mainTemplate.json.

**End-to-end verification** on `rg-vaikora-test`/`vaikora-test-ws` (ARALOC subscription):

```
az deployment group create -g rg-vaikora-test \
  --template-file Solutions/Cyren-Defender-ThreatIntelligence/Playbooks/CyrenToDefenderTI_Playbook.json \
  --parameters PlaybookName=pb-cyren-to-defender-verify-20260518 \
               workspace=vaikora-test-ws Cyren_FeedId=ip_reputation \
               Cyren_JwtToken=<real> location=eastus
  → Succeeded

# Recurrence triggered manually, 1:32 elapsed
Logic App run 08584224786523952293843448749CU75 → Succeeded
  Get_Cyren_Indicators        Succeeded   (200, 110 IOCs in NDJSON)
  Split_NDJSON_Lines          Succeeded
  Filter_Empty_Lines          Succeeded
  Check_Response_Has_Data     Succeeded
  For_Each_Indicator          Succeeded   (110 iterations)
    Parse_Record              Succeeded
    Check_Has_Identifier      Succeeded
    Post_Indicator_to_Sentinel Succeeded  (Sentinel createIndicator 200/201)
  Check_Pagination_Token      Succeeded
  Extract_Last_Offset         Succeeded
  Update_PersistentToken      Succeeded

# KQL on Sentinel ThreatIntelIndicators table, scoped to last 1h
ThreatIntelIndicators
| summarize count = count() by SourceSystem
  → 110 rows, SourceSystem = "Cyren Threat Intelligence", Type = ThreatIntelIndicators, Confidence = 50
```

Visibility fix from commit `716a1c0f` preserved (no changes to `PlaybookName` parameter rename or anywhere else outside the two `inputs` strings). `playbookVersion1` stays at `"1.0"`, `_solutionVersion` stays at `"3.0.0"`, no in-PR version bumps.
@mazamizo21
Copy link
Copy Markdown
Contributor Author

Follow-up commit c5f1c94e lands the runtime fix mentioned in the previous comment, so the same branch now ships both the visibility fix and a working pagination/empty-feed runtime.

Two surgical edits inside Poll_Cyren_Feed.actions, both in standalone Playbooks/CyrenToDefenderTI_Playbook.json and inside Package/mainTemplate.json (plus repacked 3.0.0.zip):

  1. Build_Cyren_Api_Url.inputs — switch the URL from &count=1000&queryWindowInMin=360...&token=<persistentToken> to &count=1000&offset=<persistentToken|0>. The Cyren CCF bulk-feed API at api-feeds.cyren.com/v1/feed/data ignores token and queryWindowInMin and paginates with the integer offset parameter (verified directly: calls without offset return HTTP 200 with 0 bytes).
  2. Extract_Last_Offset.inputs — wrap in @if(empty(body('Filter_Empty_Lines')), '', ...) so the workflow doesn't crash with json(null) when the feed has no new data and instead drops through to No_Data_Stop_Polling.

Manual zip edit (no V3 regen), original indentation preserved (4-space mainTemplate, 2-space standalone), only the 2 inputs strings touched per file. The visibility fix from 716a1c0f (PlaybookName parameter rename) is untouched. Package version stays at 3.0.0.

End-to-end run on vaikora-test-ws confirmed all 17 actions Succeeded and 110 fresh Cyren IOCs landed in the Sentinel ThreatIntelligence table (evidence in the previous comment).

@mazamizo21
Copy link
Copy Markdown
Contributor Author

@v-maheshbh thanks for the cleanup. Quick ask on the ReleaseNotes trim — the v3.0.0 entry dropped a few items that are customer-facing capabilities, not just internal fixes:

  • Multi-cloud support via ManagementBaseUrl from [environment().resourceManager] (commercial, government, sovereign) — Azure Government tenants actively filter on this
  • Optional JWT token with conditional deployment
  • Explicit FeedId selection
  • Per-feed Azure Marketplace trial links

Happy to drop the arm-ttk Hardcoded Uri mention (that's internal hygiene). Can we restore the four bullets above? I can push a follow-up commit if easier.

@v-atulyadav v-atulyadav merged commit 163e116 into Azure:master May 25, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-package New Solution For new Solutions which are new to Microsoft Sentinel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants