Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 25 additions & 63 deletions .claude/skills/release-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ take-ownership
check-cve-tracker-bug (always passes, notifies ART)
check-rhcos-security-alerts (Konflux only - checks blocking security alerts)
check-blocking-sec-alerts (checks blocking security alerts on RHSA advisories)
├─→ push-to-cdn-staging (async - runs independently in parallel)
└─→ [WAIT FOR BUILD PROMOTION - check API until phase == "Accepted"]
Expand Down Expand Up @@ -86,7 +86,7 @@ analyze-candidate-build (conditionally - only if accepted == false)
- **Sequential:** Most tasks run one after another
- **Parallel Execution:**
- `analyze-candidate-build` runs independently (tests already completed when flow starts)
- `push-to-cdn-staging` starts immediately after check-rhcos-security-alerts (runs while waiting for build promotion)
- `push-to-cdn-staging` starts immediately after check-blocking-sec-alerts (runs while waiting for build promotion)
- **ENHANCED:** 2 async tasks (image-consistency-check, stage-testing) triggered immediately after build promotion is detected, running in parallel with test result analysis
- **Build Promotion Checkpoint:** Critical decision point - once detected, async tasks trigger immediately
- **Test Result Checkpoints:** Must wait for file existence and aggregation (runs in parallel with async tasks)
Expand Down Expand Up @@ -327,87 +327,49 @@ stdout contains: "task [Check CVE tracker bugs] status is changed to [Pass]"

**Expected Duration:** 1 minute

**Next Action:** Proceed to check-rhcos-security-alerts
**Next Action:** Proceed to check-blocking-sec-alerts

---

### 4. check-rhcos-security-alerts
### 4. check-blocking-sec-alerts

**Purpose:** Check for blocking security alerts on RHCOS advisory (Konflux flow only)

**When to run:** Konflux release flow only (releases with shipment_mr in metadata)
**Purpose:** Check for blocking security alerts across all RHSA advisories

**Prerequisites:** check-cve-tracker-bug completed

**Implementation:** Uses curl with Kerberos authentication (no existing MCP tool)

**Execution Steps:**

**Step 1: Verify Kerberos ticket exists**
```bash
klist
```
**MCP Tool:** `oar_check_blocking_sec_alerts(release)`

If no ticket or ticket expired:
- Report to user: "No valid Kerberos ticket found. Please run: kinit $kid@$domain"
- STOP task execution
**Input:**
- `release`: Z-stream version

**Step 2: Get RHCOS advisory ID**
**Execution:**
```python
metadata = oar_get_release_metadata(release)
rhcos_advisory_id = metadata.advisories.rhcos
result = oar_check_blocking_sec_alerts(release=release)
```

**Step 3: Fetch security alerts from Errata Tool**
```bash
curl -s -u : --negotiate 'https://errata.devel.redhat.com/api/v1/erratum/{rhcos_advisory_id}/security_alerts'
```
**Output Parsing:**
```text
IF "BLOCKING SECURITY ALERTS FOUND" in stdout:
Report blocking advisory details to user
Advise user to contact secalert@redhat.com
Task status will be Fail - a blocking issue is added to StateBox

**Step 4: Parse response and check for blocking alerts**
```python
response = json.loads(curl_output)

# Filter blocking alerts from the alerts array
blocking_alerts = [alert for alert in response.alerts.alerts if alert.blocking == true]

IF len(blocking_alerts) > 0:
Report to user with alert details and ask to email secalert@redhat.com
# Continue pipeline - this is not a hard blocker, but requires follow-up

ELSE:
Report to user: "No blocking security alerts found on RHCOS advisory"
IF "No blocking security alerts found" in stdout:
Report: "No blocking security alerts found"
Task status will be Pass
```

**Success Detection:**
```
Task always passes - this is an informational check
Blocking alerts require manual follow-up but don't stop the pipeline
```text
stdout contains: "task [Check Blocking Security Alerts] status is changed to [Pass]"
```

**Expected Duration:** 10 seconds

**Errata Tool API Response Format:**
```json
{
"alerts": {
"alerts": [
{
"name": "erratum_missing_notes_link",
"text": "...",
"description": "...",
"how_to_resolve": "...",
"blocking": false
}
],
"blocking": false
}
}
**Failure Detection:**
```text
stdout contains: "task [Check Blocking Security Alerts] status is changed to [Fail]"
```

**Key Fields:**
- `.alerts.alerts[]` (array) - List of individual alerts
- `.alerts.alerts[].blocking` (boolean) - Per-alert blocking status (THIS is what we check)
- `.alerts.blocking` (boolean) - Top-level blocking status (informational only)
**Expected Duration:** 30 seconds

**Next Action:**
- Trigger push-to-cdn-staging (async)
Expand Down
17 changes: 17 additions & 0 deletions mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,23 @@ async def oar_check_cve_tracker_bug(release: str, notify: bool = False) -> str:
return format_result(result)


@mcp.tool()
async def oar_check_blocking_sec_alerts(release: str) -> str:
"""
Check for blocking security alerts across all RHSA advisories.

WRITE OPERATION: Creates blocking issue in StateBox when alerts are found.

Args:
release: Z-stream release version (e.g., "4.19.1")

Returns:
Blocking security alert check results
"""
result = await invoke_oar_command_async(release, "check-blocking-sec-alerts", [])
return format_result(result)


@mcp.tool()
async def oar_image_signed_check(release: str) -> str:
"""
Expand Down
112 changes: 112 additions & 0 deletions oar/cli/cmd_check_blocking_sec_alerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import logging

import click

from oar.core.advisory import AdvisoryManager
from oar.core.const import (
TASK_CHECK_BLOCKING_SEC_ALERTS,
TASK_STATUS_FAIL,
TASK_STATUS_INPROGRESS,
TASK_STATUS_PASS,
)
from oar.core.statebox import StateBox
from oar.core.exceptions import StateBoxException
from oar.core import util

logger = logging.getLogger(__name__)


@click.command()
@click.pass_context
def check_blocking_sec_alerts(ctx):
"""
Check for blocking security alerts across all RHSA advisories.
Reports blocking alerts and updates StateBox task status.
"""
cs = ctx.obj["cs"]

try:
util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_INPROGRESS)

am = AdvisoryManager(cs)
statebox = StateBox(cs)
advisories = am.get_advisories()

rhsa_advisories = [ad for ad in advisories if ad.errata_type == "RHSA"]
if not rhsa_advisories:
logger.info("No RHSA advisories found, skipping security alert check")
util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_PASS)
return
Comment thread
coderabbitai[bot] marked this conversation as resolved.

logger.info(f"Found {len(rhsa_advisories)} RHSA advisory(ies) to check: "
f"{', '.join(str(ad.errata_id) for ad in rhsa_advisories)}")

blocking_advisories = []
check_errors = {}

for advisory in rhsa_advisories:
try:
if advisory.has_blocking_security_alert():
blocking_advisories.append(advisory)
except Exception as e:
check_errors[advisory.errata_id] = str(e)
logger.error(f"Error checking advisory {advisory.errata_id}: {e}")

existing_issues = statebox.get_issues(
unresolved_only=True, task_name=TASK_CHECK_BLOCKING_SEC_ALERTS
)

if blocking_advisories:
logger.warning("BLOCKING SECURITY ALERTS FOUND:")
advisory_details = []
for advisory in blocking_advisories:
link = util.get_advisory_link(str(advisory.errata_id))
logger.warning(f" RHSA advisory {advisory.errata_id} - {link}")
advisory_details.append(f"- RHSA advisory {advisory.errata_id}: {link}")

issue_description = (
f"Found blocking security alerts in {len(blocking_advisories)} RHSA advisory(ies)\n\n"
f"Affected advisories:\n" + "\n".join(advisory_details)
)

existing_blocker = next(
(i for i in existing_issues if i.get("blocker", False)), None
)
if existing_blocker and existing_blocker["issue"].strip().lower() == issue_description.strip().lower():
logger.info("Blocking security alerts unchanged from previous check")
else:
if existing_blocker:
statebox.resolve_issue(
issue=existing_blocker["issue"],
resolution="Replaced with updated blocking security alert findings",
)
try:
statebox.add_issue(
issue=issue_description,
blocker=True,
related_tasks=[TASK_CHECK_BLOCKING_SEC_ALERTS],
auto_save=True,
)
logger.info(f"Created blocking issue in StateBox for {len(blocking_advisories)} RHSA advisory(ies)")
except StateBoxException as e:
logger.warning(f"Could not add StateBox issue: {e}")

util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_FAIL)
elif check_errors:
error_details = "; ".join(f"{eid}: {err}" for eid, err in check_errors.items())
logger.warning(f"Security-alert checks failed for: {error_details}")
util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_FAIL)
else:
for issue in existing_issues:
statebox.resolve_issue(
issue=issue["issue"],
resolution="No blocking security alerts found on re-check",
)
logger.info(f"Resolved previous StateBox issue: {issue['issue'][:80]}")
logger.info("No blocking security alerts found")
util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_PASS)

except Exception as e:
logger.exception("check blocking sec-alerts failed")
util.log_task_status(TASK_CHECK_BLOCKING_SEC_ALERTS, TASK_STATUS_FAIL)
raise
2 changes: 2 additions & 0 deletions oar/cli/cmd_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import oar.core.util as util
from oar import version
from oar.cli.cmd_change_advisory_status import change_advisory_status
from oar.cli.cmd_check_blocking_sec_alerts import check_blocking_sec_alerts
from oar.cli.cmd_check_cve_tracker_bug import check_cve_tracker_bug
from oar.cli.cmd_create_test_report import create_test_report
from oar.cli.cmd_image_consistency_check import image_consistency_check
Expand Down Expand Up @@ -227,6 +228,7 @@ def cli(ctx, release, debug):
cli.add_command(take_ownership)
cli.add_command(image_consistency_check)
cli.add_command(check_cve_tracker_bug)
cli.add_command(check_blocking_sec_alerts)
cli.add_command(push_to_cdn_staging)
cli.add_command(stage_testing)
cli.add_command(image_signed_check)
Expand Down
4 changes: 2 additions & 2 deletions oar/core/advisory.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def change_advisory_status(self, target_status=AD_STATUS_REL_PREP):
logger.warning(
f"cannot change state of advisory {ad.errata_id} from {target_status} to {ad.get_state()}, skip")
continue
if ad.has_blocking_secruity_alert():
if ad.has_blocking_security_alert():
raise AdvisoryException(
f"advisory {ad.errata_id} has blocking secalerts, please contact prodsec team")
ad.set_state(target_status.strip())
Expand Down Expand Up @@ -654,7 +654,7 @@ def refresh_security_alerts(self):
f"RHBA advisory {self.errata_id} does not have secalerts")
return None

def has_blocking_secruity_alert(self):
def has_blocking_security_alert(self):
"""
Check RHSA advisory has blocking security alert
"""
Expand Down
4 changes: 4 additions & 0 deletions oar/core/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"analyze-promoted-build",
"check-greenwave-cvp-tests",
"check-cve-tracker-bug",
"check-blocking-sec-alerts",
"push-to-cdn-staging",
"stage-testing",
"image-signed-check",
Expand All @@ -80,6 +81,7 @@
"analyze-candidate-build",
"analyze-promoted-build",
"check-cve-tracker-bug",
"check-blocking-sec-alerts",
"push-to-cdn-staging",
"stage-testing",
"image-signed-check",
Expand Down Expand Up @@ -108,6 +110,7 @@
TASK_IMAGE_SIGNED_CHECK = "image-signed-check"
TASK_DROP_BUGS = "drop-bugs"
TASK_CHANGE_ADVISORY_STATUS = "change-advisory-status"
TASK_CHECK_BLOCKING_SEC_ALERTS = "check-blocking-sec-alerts"

# Task to human-readable display name mapping
TASK_DISPLAY_NAMES = {
Expand All @@ -122,6 +125,7 @@
TASK_IMAGE_SIGNED_CHECK: "Image Signed Check",
TASK_DROP_BUGS: "Drop Bugs",
TASK_CHANGE_ADVISORY_STATUS: "Change Advisory Status",
TASK_CHECK_BLOCKING_SEC_ALERTS: "Check Blocking Security Alerts",
}

# env variables
Expand Down
Loading