Skip to content

Migrate SecurityScorecard Cybersecurity Ratings solution to CCF (multi-instance + Azure Function parity) - #14757

Open
sigmabaryon wants to merge 9 commits into
Azure:masterfrom
leeninc:ssc-ratings-ccf
Open

Migrate SecurityScorecard Cybersecurity Ratings solution to CCF (multi-instance + Azure Function parity)#14757
sigmabaryon wants to merge 9 commits into
Azure:masterfrom
leeninc:ssc-ratings-ccf

Conversation

@sigmabaryon

Copy link
Copy Markdown

Change(s)

Migrate the SecurityScorecard Cybersecurity Ratings solution from the Python Function App data connectors to the Codeless Connector Framework (CCF), with multi-instance support and feature parity with the legacy Azure Function connectors. Solution stays at version 3.0.0 (pre-publication rework).

Data connectors (Ratings, Factor, Issue) — new CCF _ccp/ bundles

  • Multi-instance UI (DataConnectorsGrid + ContextPane): customers add one independently-managed connection per SecurityScorecard portfolio, listed/deleted independently. Each connection is scoped server-side to /portfolios/{PortfolioId}/companies, with per-connection ARM resource names via uniqueString(parameters('PortfolioId')) so instances coexist in one workspace.
  • Real dated history for all three: nested pollers read the per-company history endpoints (/companies/{domain}/history/score, /history/factors/score, /history/events) and map the API date to score_date/TimeGenerated (no now() snapshotting).
  • Own-company "My Scorecard": an optional Domain field drives a second, condition-gated flat poller per connector that ingests your own company's history, tagged portfolio_name = "My Scorecard".
  • domain and industry are carried into the nested leaf events via addOnAttributes (projected from the portfolio-companies listing), so Factor/Issue no longer depend on a cross-connector lookup for enrichment.
  • DCR streams/transforms written to the real SecurityScorecard API fields; breach issues filtered pre-ingest; logResponseContent disabled; queryWindowInMin set to 1440 (daily).

Parsers (SecurityScorecardRatings/Factor/Issue.yaml)

  • Each unions a Legacy view over the old *_CL tables with a CCF view over the new *History_CL tables, preserving backward compatibility for existing data.
  • Day-over-day deltas computed with prev() partitioned by portfolio_id; Factor exposes per-factor metadata via a datatable; Issue dedups the rolling poll window by (id, portfolio_id, domain, issue_date) and humanizes issueName_s.

Workbook (SecurityScorecardWorkbook.json)

  • TimeGenerated in place of ingestion_time(); 30-day panel window.
  • The Domain picker now sources only own-company rows (portfolioName_s in ('NA','My Scorecard')) and is optional; the Vendor Scores panels continue to exclude the own company by design.

Legacy Function App connectors

  • Marked [DEPRECATED] (title + description), retained for backward compatibility. The deprecated-title (using Azure Functions) suffix is now applied once by the packaging tool.

Packaging / cleanup

  • Repackaged 3.0.0 (Package/mainTemplate.json, createUiDefinition.json, 3.0.0.zip regenerated by the V3 tool).
  • Removed the stale azuredeploy_*_poller_connector.json templates and the leftover Package/2.0.0.zip.
  • Fixed the CCF poller resource-name expression to quote the workspace parameter reference (concat('parameters('workspace')', ...)), matching merged CCF connectors — required for the deployment to validate.

Reason for Change(s)

The Python Function App connectors depend on the deprecated HTTP Data Collector API and require an Azure Function App, storage account, and stored checkpoints to operate. Moving Ratings/Factor/Issue ingestion to native codeless pollers removes that hosting dependency, ingests on the workspace's own cloud via DCE/DCR (fixing the legacy connectors' broken Gov-cloud path), and adds multi-portfolio and own-company ingestion. The parsers preserve backward compatibility so existing *_CL data continues to query alongside the new CCF data.

Version Updated

  • N/A for the Detections/Analytic Rule version requirement — this solution ships no analytic rules.
  • Solution remains 3.0.0 (rework of an unpublished release; no bump within the open PR). ReleaseNotes.md and Data/Solution_SecurityScorecard.json are in sync at 3.0.0.

Testing Completed

  • Yes. Deployed the packaged template to a live Microsoft Sentinel workspace and connected the CCF connectors. All three tables populate against the live SecurityScorecard API — SecurityScorecardRatingsHistory_CL, SecurityScorecardFactorHistory_CL, SecurityScorecardIssuesHistory_CL — with domain, industry, portfolio_id, and portfolio_name present, and record counts matching the API responses. Multi-instance (one connection per portfolio) and the parser → workbook path verified end to end.

Checked that the validations are passing and have addressed any issues that are present

  • Yes. Ran .script/local-validation/build-and-validate.ps1 against the solution: ARM-TTK (48/48), the TypeScript validators (JSON/YAML/data-connector schema/workbook/branding), Field Types, and Classic App Insights all pass. KQL/Detection/Non-ASCII are skipped locally (no .NET runtime) and run in CI.
  • One known false positive: the Hyperlink validator flags https://api.securityscorecard.io — the SecurityScorecard API base URL placeholder/instruction text (an API root that returns 404 to a browser). It is pre-existing content and not a broken documentation link.

Known limitations / follow-ups

  • Level {…} Change (a static severity label) and the Diff Override change-only-ingestion toggle from the Function App are not reproduced; the shipped defaults are equivalent, and change-only can be expressed at query time (| where scoreChange_d != 0).
  • Factor/issue-type friendly names come from static parser tables rather than live /metadata calls (CCF cannot make auxiliary enrichment calls); re-sync if SecurityScorecard adds or renames factors/issue types.
  • Configurable table names and cron-level scheduling are not exposed by CCF (DCR streams are static; queryWindowInMin is the cadence lever).

…card CCF connectors

Convert the Ratings, Factor, and Issue CCF connectors to multi-instance
(DataConnectorsGrid + ContextPane) with one independently-managed connection
per portfolio, scoped server-side via uniqueString pollers, and close the
remaining feature gaps versus the legacy Azure Function connectors:

- Ratings: nested /companies/{domain}/history/score for real dated history
  (replaces the now() snapshot), with real day-over-day deltas and backfill.
- Optional own-company "My Scorecard" ingestion via a condition-gated poller
  per connector (optional Domain field).
- Inject company domain and industry into Factor/Issue leaf events via
  addOnAttributes; drop the cross-connector RatingsHistory parser lookup.
- Issue parser: dedup by (id, portfolio_id); humanize issueName.
- Ratings/Factor parsers: key day-over-day partitioning on portfolio_id.
- Disable logResponseContent; remove stale azuredeploy poller templates.
- Repackage solution at 3.0.0.
…rence

The CCF deployment path substitutes the literal text parameters('workspace')
with the workspace name, so the reference must be wrapped in single quotes to
produce a valid ARM string literal. Without them the expression resolved to
concat(ssc-ccf-wkspc-01, ...) -- a bare token -- and deployment failed with
InvalidTemplate ("the string character 'c' at position '11' is not expected").

Matches the form used by the merged ElasticAgent CCF connector. Applies to all
six pollers (portfolio + own-domain across Ratings, Factor, and Issue).
Repackaged 3.0.0.
The Vendor Scores panels exclude the selected Domain by design
(subject_s != '{Domain}'), because {Domain} denotes your own company rather
than a filter -- vendors are everyone except you. The CCF migration repointed
the picker at all companies (the legacy "NA" own-domain marker was never
produced by CCF), so selecting any entry removed it from the results.

Source the picker from own-company rows only -- "NA" from the legacy Function
connector and "My Scorecard" from the new CCF own-domain poller -- and make it
optional so the vendor pages still render when no own-domain is configured.

Repackaged 3.0.0.
…or titles

The V3 packaging tool appends " (using Azure Functions)" to Function App
connector titles, guarding on the plural string "using Azure Functions". The
deprecation titles used the singular "(using Azure Function)", so the guard
missed and the suffix was appended a second time, rendering as
"... (using Azure Function) (using Azure Functions)".

Drop the suffix from the source titles and let the packaging tool add it.
Repackaged 3.0.0.
…dedup

- Domain textbox declared no validations block, so the packaging tool fell
  through to its default and emitted the parameter as
  {defaultValue: "Domain", minLength: 1}. A user who left the field blank --
  as its own label instructs -- submitted "" and failed minLength, taking down
  the entire connection deployment including the portfolio poller. Adding
  validations.required:false makes the generated parameter defaultValue ""
  with no minLength, per CCF_README.

- queryWindowInMin 10080 -> 1440 on all six pollers. SecurityScorecard was the
  only solution in the repository above 1440 (70 others use 1440); the
  underlying data is daily, so a 7-day window meant either weekly polling or
  ~7x redundant API volume against rateLimitQPS 1.

- Widen the Issue parser dedup key from (id, portfolio_id) to
  (id, portfolio_id, domain, issue_date). The narrower key collapsed
  active->resolved state transitions -- breaking the workbook's Active/Resolved
  sums -- and could collide across companies if event ids are not globally
  unique. The workbook's own dedup expressions all include subject_s.

- factor_score: drop the tolong() wrapper, which floored fractional factor
  scores.

Repackaged 3.0.0.
@sigmabaryon

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Leen"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Migrates the SecurityScorecard Cybersecurity Ratings solution from legacy Azure Function App connectors to Codeless Connector Framework (CCF) connectors with multi-instance support, and updates parsers/workbook for backward-compatible querying.

Changes:

  • Added new CCF connector bundles (tables, DCRs, polling configs, connector definitions) plus “My Scorecard” optional ingestion.
  • Added/updated KQL parsers to union legacy *_CL with new *History_CL tables and updated workbook queries accordingly.
  • Updated solution packaging metadata (solution data file, UI definition, workbook metadata) and marked legacy connectors as deprecated.

Reviewed changes

Copilot reviewed 25 out of 28 changed files in this pull request and generated 32 comments.

Show a summary per file
File Description
Workbooks/WorkbooksMetadata.json Updates workbook dependencies to include new CCF tables/connectors.
Solutions/SecurityScorecard Cybersecurity Ratings/Workbooks/SecurityScorecardWorkbook.json Updates time ranges/parameters and migrates workbook queries to parser-based tables.
Solutions/SecurityScorecard Cybersecurity Ratings/SolutionMetadata.json Adjusts solution identifiers/categories metadata.
Solutions/SecurityScorecard Cybersecurity Ratings/ReleaseNotes.md Adds release notes for the 3.0.0 migration.
Solutions/SecurityScorecard Cybersecurity Ratings/Parsers/SecurityScorecardRatings.yaml Adds Ratings parser that unions legacy and CCF history with deltas.
Solutions/SecurityScorecard Cybersecurity Ratings/Parsers/SecurityScorecardIssue.yaml Adds Issues parser that unions legacy and CCF history.
Solutions/SecurityScorecard Cybersecurity Ratings/Parsers/SecurityScorecardFactor.yaml Adds Factors parser that unions legacy and CCF history plus metadata lookup.
Solutions/SecurityScorecard Cybersecurity Ratings/Package/testParameters.json Adds ARM test parameters for packaging/validation.
Solutions/SecurityScorecard Cybersecurity Ratings/Package/createUiDefinition.json Updates installation UX copy and lists new CCF connectors/parsers.
Solutions/SecurityScorecard Cybersecurity Ratings/Data/Solution_SecurityScorecard.json Registers new CCF connectors and parsers; updates solution version to 3.0.0.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardRatings_ccp/SecurityScorecardRatings_tables.json Adds new SecurityScorecardRatingsHistory_CL table schema.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardRatings_ccp/SecurityScorecardRatings_PollingConfig.json Adds CCF pollers for portfolio + optional “My Scorecard” ratings ingestion.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardRatings_ccp/SecurityScorecardRatings_DataConnectorDefinition.json Adds customizable CCF connector definition for Ratings.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardRatings_ccp/SecurityScorecardRatings_DCR.json Adds DCR stream/transform for ratings history ingestion.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardRatings/SecurityScorecardRatings_API_FunctionApp.json Marks legacy Ratings Function App connector as deprecated.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardIssue_ccp/SecurityScorecardIssue_tables.json Adds new SecurityScorecardIssuesHistory_CL table schema.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardIssue_ccp/SecurityScorecardIssue_PollingConfig.json Adds CCF pollers for portfolio + optional “My Scorecard” issues ingestion.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardIssue_ccp/SecurityScorecardIssue_DataConnectorDefinition.json Adds customizable CCF connector definition for Issues.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardIssue_ccp/SecurityScorecardIssue_DCR.json Adds DCR stream/transform for issues history ingestion (incl. pre-filter).
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardIssue/SecurityScorecardIssue_API_FunctionApp.json Marks legacy Issues Function App connector as deprecated.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardFactor_ccp/SecurityScorecardFactor_tables.json Adds new SecurityScorecardFactorHistory_CL table schema.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardFactor_ccp/SecurityScorecardFactor_PollingConfig.json Adds CCF pollers for portfolio + optional “My Scorecard” factor ingestion.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardFactor_ccp/SecurityScorecardFactor_DataConnectorDefinition.json Adds customizable CCF connector definition for Factors.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardFactor_ccp/SecurityScorecardFactor_DCR.json Adds DCR stream/transform for factor history ingestion.
Solutions/SecurityScorecard Cybersecurity Ratings/Data Connectors/SecurityScorecardFactor/SecurityScorecardFactor_API_FunctionApp.json Marks legacy Factors Function App connector as deprecated.

Comment thread Solutions/SecurityScorecard Cybersecurity Ratings/ReleaseNotes.md Outdated
Register the three CCF history tables with the KQL validator. The
solution parsers reference SecurityScorecardRatingsHistory_CL,
SecurityScorecardFactorHistory_CL and SecurityScorecardIssuesHistory_CL,
which have no schema under KqlvalidationsTests/CustomTables, so
Validate_SolutionParsersFunctions_HaveValidKql fails all three with
"does not refer to any known table, tabular variable or function".
Schemas mirror each CCF bundle's *_tables.json.

Also fix the IssueType picker projecting "lable" instead of "label", so
the dropdown renders the humanized factor names rather than falling back
to the raw factor keys. Package regenerated to carry the workbook change;
solution version stays at 3.0.0.
@sigmabaryon
sigmabaryon requested a review from a team as a code owner July 27, 2026 14:02
@v-maheshbh

Copy link
Copy Markdown
Contributor

Hi @sigmabaryon

Kindly retain the existing ZIP file and avoid deleting it during the update and review all Copilot comments and mark them as resolved, as appropriate and update release notes with latest changes.

image

Thanks!

Address review feedback from @v-maheshbh:

- Restore Package/2.0.0.zip, which was removed when 3.0.0 was generated.
  Prior version archives are retained across the repo (BitSight keeps
  seven, Cloudflare seven, ElasticAgent two), so the existing zip stays
  alongside the new one.
- Add the 2.0.0 row to ReleaseNotes.md so the retained archive has a
  matching entry, and refresh the 3.0.0 date to the current publish date.
@sigmabaryon

sigmabaryon commented Jul 28, 2026

Copy link
Copy Markdown
Author

Hi @v-maheshbh — thanks for the review. All three points are addressed

  1. Existing ZIP retained — Package/2.0.0.zip is restored and now sits alongside 3.0.0.zip. The PR no longer deletes any file.
  2. Copilot comments — all 32 threads reviewed and resolved, with a reply on each distinct finding:
    • 27 comments flagged [[...] in the *_PollingConfig.json files as unbalanced ARM. This is the required CCF source convention — the leading [[ is ARM's escape for a literal [, so the packaged mainTemplate.json emits [concat(...)] into the nested deployment for evaluation at connect time. 210 occurrences of this form exist across 36 *PollingConfig.json files repo-wide and none use ]]; run-arm-ttk passes 48/48 and the connectors are deployed and ingesting against a live workspace.
    • Workbook Domain parameter: type: 2 is the query-backed dropdown (type: 1 is free text), matching every other picker in this workbook.
    • ReleaseNotes table: single leading pipe — the apparent || was the diff's own + marker column.
    • lable → label: valid catch, fixed in 84c6288.
    • SolutionMetadata.json: "Security" is absent from ValidDomainsVerticals.json, so it was corrected to "Security - Others". The publisherId/offerId lowercasing follows the documented ^[a-z][a-z0-9]{0,49}$ rule, but it changes variables('solutionId') from SecurityScorecard.SecurityScorecard to securityscorecard.securityscorecard, which flows into _solutioncontentProductId through a case-sensitive uniqueString(). Could you confirm which you'd prefer? Happy to revert if the published identity needs preserving for the 2.0.0 → 3.0.0 upgrade path.
  3. Updated Release notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants