Skip to content

ci: add GitHub issues to Azure DevOps sync workflow#527

Open
Yetkin Timocin (ytimocin) wants to merge 2 commits into
kubefleet-dev:mainfrom
ytimocin:ci/sync-issues-to-ado
Open

ci: add GitHub issues to Azure DevOps sync workflow#527
Yetkin Timocin (ytimocin) wants to merge 2 commits into
kubefleet-dev:mainfrom
ytimocin:ci/sync-issues-to-ado

Conversation

@ytimocin

@ytimocin Yetkin Timocin (ytimocin) commented Mar 19, 2026

Copy link
Copy Markdown
Collaborator

Description of your changes

This pull request introduces a new GitHub Actions workflow, .github/workflows/sync-issues-to-ado.yml, that securely and automatically syncs approved GitHub issues to Azure DevOps (ADO) as work items. The workflow emphasizes security, least privilege, and supply-chain safety by using direct API calls and robust input validation. It only syncs issues labeled with ado-sync by maintainers, and provides a manual trigger for ad-hoc syncing.

Security and Trust Boundaries

  • Only issues labeled ado-sync by maintainers are eligible for syncing. Manual syncs are also label-gated to prevent bypasses.
  • The workflow is scoped to run only on the upstream repository, not forks, and the sync job declares environment: issues so secrets sit behind environment-level access approval per the secure-use guidance. The required-reviewers rule on that environment is what makes the gate non-decorative — see "Before first run" below.
  • All user and input data (issue numbers, titles, bodies) is validated and safely escaped to prevent shell, script, HTML, and WIQL injection (env-var pass-through, jq --arg, @html escaping, random-delimiter heredocs for GITHUB_OUTPUT).

Supply-Chain and Permissions Hardening

  • Replaces third-party ADO actions with direct REST API calls, removing that supply-chain link.
  • Pinned actions/github-script to a full-length commit SHA.
  • Uses permissions: { issues: write } at workflow level. Splitting into two jobs so the ADO PAT and the GitHub-write token never coexist in one process is tracked in [Feature] Harden ado-sync workflow per secure-use guidance #680.

Review feedback addressed (commit bda8ac7f)

  • Gate labeled events on github.event.label.name == 'ado-sync' so adding any other label to an ado-sync issue no longer re-triggers a sync.
  • Paginate listComments so the link-back marker is found even when an issue has >30 comments.
  • URL-encode ADO_ORG and ADO_PROJECT in both the curl base URL and the JS link-back URL.
  • Validate ISSUE_NUMBER is strictly numeric before any gh issue view call on the manual-dispatch path.

I have:

How has this code been tested

Locally tested with act:

act issues \
  -e test-issue-event.json \
  -W .github/workflows/sync-issues-to-ado.yml \
  -n \
  --container-architecture linux/amd64 \
  --env GITHUB_REPOSITORY=kubefleet-dev/kubefleet
  • -e test-issue-event.json — fake issue event payload
  • -n — dry run (no containers actually started)
  • --env GITHUB_REPOSITORY=... — overrides the repo guard so the job isn't skipped on a fork

The workflow has not yet run end-to-end against a real ADO instance. The first labelled issue post-merge is the first true e2e, so the configuration in "Before first run" must be in place before any issue gets labelled ado-sync in production.

Before first run (required configuration)

The job declares environment: issues, so the secrets below should be configured on that environment (not just at repo level) so they inherit the environment's access-approval rule.

Environment secrets to add to the issues environment (only names are listed; values are set in repo settings — never paste real values into a PR/issue/comment):

Name Purpose
ADO_PAT Azure DevOps Personal Access Token. Scope: Work Items: Read & Write. Project-scoped to the target project. Set an expiration.
ADO_ORGANIZATION Azure DevOps organization (URL path component).
ADO_PROJECT Azure DevOps project name. Used for the URL path and the WIQL [System.TeamProject] filter.
ADO_AREA_PATH System.AreaPath value applied to new work items.
ADO_ITERATION_PATH System.IterationPath value applied to new work items.

The auto-provided GITHUB_TOKEN (referenced as ${{ github.token }} and implicitly used by actions/github-script) does not need to be added — it is issued per run.

Repo configuration (no YAML, all in Settings):

  1. Create the issues environment under Settings → Environments. Configure all five secrets above on it.
  2. Configure the required reviewers rule on issues (per the secure-use guidance). Without it, the environment gate is decorative — anyone with write access could trigger a workflow_dispatch and reach the PAT.
  3. Restrict the environment's deployment branch policy to main, so a workflow_dispatch from a feature branch cannot run a modified workflow that exfiltrates the PAT.
  4. Create the ado-sync label in the repo. The workflow gates on it; without the label no issue is syncable.
  5. Designate a maintainer (or rotation team) responsible for ADO_PAT rotation before its expiration.

Bonus pre-flight verification:

  • Confirm the configured ADO_PAT authenticates against ADO (one-shot curl against _apis/projects returns 200) before labelling the first issue.
  • After the first real run, manually skim the workflow run logs to confirm no unredacted secret material appears (per the secure-use page's log-redaction guidance).

Special notes for your reviewer

Follow-up hardening tracked in #680: job split for tighter permissions scoping, GitHub-App-vs-PAT evaluation for the ADO credential, and CODEOWNERS for .github/workflows/.

@codecov

codecov Bot commented Mar 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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

Adds a new GitHub Actions workflow to synchronize maintainer-approved GitHub issues into Azure DevOps (ADO) work items, using direct REST API calls and a label/environment gate to control trust boundaries.

Changes:

  • Introduces .github/workflows/sync-issues-to-ado.yml to sync issues labeled ado-sync (and support manual workflow_dispatch).
  • Implements ADO work item create/update + state mapping via curl/jq and a WIQL lookup keyed by a GitHub-derived tag.
  • Posts a link-back comment on the GitHub issue pointing to the corresponding ADO work item.

Comment thread .github/workflows/sync-issues-to-ado.yml Outdated
Comment thread .github/workflows/sync-issues-to-ado.yml Outdated
Comment thread .github/workflows/sync-issues-to-ado.yml Outdated

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/sync-issues-to-ado.yml Outdated
@ytimocin
Yetkin Timocin (ytimocin) force-pushed the ci/sync-issues-to-ado branch 3 times, most recently from 47c1061 to 8d1e5f0 Compare April 23, 2026 18:12
- name: Resolve issue details
id: issue
env:
GH_TOKEN: ${{ github.token }}

Choose a reason for hiding this comment

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

💡 Thoughts, only - but something you/we should consider https://docs.github.com/en/actions/tutorials/authenticate-with-github_token

# [Workflow dispatch] Manual trigger for ad-hoc syncing of specific issues
workflow_dispatch:
inputs:
issue_number:

Choose a reason for hiding this comment

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

💡Thought Share: Consider requiring review for access to secrets

https://docs.github.com/en/actions/reference/security/secure-use

Yetkin Timocin (ytimocin) added a commit to ytimocin/kubefleet that referenced this pull request Apr 28, 2026
Address inline review comments on PR kubefleet-dev#527:

* Gate the `labeled` event on `github.event.label.name == 'ado-sync'`
  so adding any unrelated label to an issue that already has `ado-sync`
  no longer re-triggers a sync. Closed/reopened keep the
  label-presence check because the action payload omits `label.name`
  there.

* Paginate the `listComments` call before the link-back-marker dedupe
  check, otherwise the marker is missed when the issue has more than
  30 comments and the workflow posts a duplicate.

* URL-encode `ADO_ORG` and `ADO_PROJECT` in both the curl base URL and
  the JS link-back URL. ADO project names commonly contain spaces or
  reserved URL characters; the previous form would emit malformed
  request URLs in those cases.

* Validate that `ISSUE_NUMBER` is strictly numeric BEFORE the
  workflow_dispatch path's `gh issue view` call, so malformed manual
  input fails fast with the explicit error rather than tunnelling
  through an unrelated `gh` failure.

Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/sync-issues-to-ado.yml
Yetkin Timocin (ytimocin) added a commit to ytimocin/kubefleet that referenced this pull request Apr 29, 2026
Address inline review comments on PR kubefleet-dev#527:

* Gate the `labeled` event on `github.event.label.name == 'ado-sync'`
  so adding any unrelated label to an issue that already has `ado-sync`
  no longer re-triggers a sync. Closed/reopened keep the
  label-presence check because the action payload omits `label.name`
  there.

* Paginate the `listComments` call before the link-back-marker dedupe
  check, otherwise the marker is missed when the issue has more than
  30 comments and the workflow posts a duplicate.

* URL-encode `ADO_ORG` and `ADO_PROJECT` in both the curl base URL and
  the JS link-back URL. ADO project names commonly contain spaces or
  reserved URL characters; the previous form would emit malformed
  request URLs in those cases.

* Validate that `ISSUE_NUMBER` is strictly numeric BEFORE the
  workflow_dispatch path's `gh issue view` call, so malformed manual
  input fails fast with the explicit error rather than tunnelling
  through an unrelated `gh` failure.

Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
Address inline review comments on PR kubefleet-dev#527:

* Gate the `labeled` event on `github.event.label.name == 'ado-sync'`
  so adding any unrelated label to an issue that already has `ado-sync`
  no longer re-triggers a sync. Closed/reopened keep the
  label-presence check because the action payload omits `label.name`
  there.

* Paginate the `listComments` call before the link-back-marker dedupe
  check, otherwise the marker is missed when the issue has more than
  30 comments and the workflow posts a duplicate.

* URL-encode `ADO_ORG` and `ADO_PROJECT` in both the curl base URL and
  the JS link-back URL. ADO project names commonly contain spaces or
  reserved URL characters; the previous form would emit malformed
  request URLs in those cases.

* Validate that `ISSUE_NUMBER` is strictly numeric BEFORE the
  workflow_dispatch path's `gh issue view` call, so malformed manual
  input fails fast with the explicit error rather than tunnelling
  through an unrelated `gh` failure.

Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants