Skip to content

[cisco_ios] Fix script_timezone crash for unrecognised TZ abbreviations. - #20733

Open
ie-ops wants to merge 7 commits into
mainfrom
fix/0-in-the-get-timezone-painless-function-default-yml-30976768
Open

[cisco_ios] Fix script_timezone crash for unrecognised TZ abbreviations.#20733
ie-ops wants to merge 7 commits into
mainfrom
fix/0-in-the-get-timezone-painless-function-default-yml-30976768

Conversation

@ie-ops

@ie-ops ie-ops commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Executive summary

The fix addresses a failure in the script_timezone processor when Cisco IOS log messages contain timezone abbreviations (e.g. SGT, ICT, NPT) that Java's SimpleDateFormat cannot resolve. Previously, an unhandled exception would crash the processor. The fix adds a built-in IANA zone-ID lookup map for 9 common abbreviations, wraps the SimpleDateFormat call in a try/catch so unrecognised abbreviations fall back gracefully to tz_offset or UTC, preserves the original abbreviation in event.timezone, and tags affected documents with cisco_ios_unmapped_timezone to aid operator discovery.

Proposed commit message

[cisco_ios] Fix script_timezone crash for unrecognised TZ abbreviations.

Root cause

The script_timezone Painless processor calls SimpleDateFormat('z').parse(event_timezone) without a try/catch, but Java's JDK only recognises a limited set of timezone abbreviations and throws ParseException for valid Cisco IOS-XR abbreviations common in APAC deployments (SGT, ICT, and others), causing the entire processor — including the cisco_timestamp append that feeds the date processor — to fail and routing the event to on_failure.

Approach

In the get_timezone() Painless function (default.yml lines 141–168), insert a hardcoded built-in IANA zone-ID lookup (SGT→Asia/Singapore, ICT→Asia/Bangkok, MYT→Asia/Kuala_Lumpur, PHT→Asia/Manila, TRT→Europe/Istanbul, BRT→America/Sao_Paulo, ART→America/Argentina/Buenos_Aires, CLT→America/Santiago, PET→America/Lima) immediately after the 'Z' check and before the length() <= 4 check, so matched abbreviations return canonical DST-aware zone IDs that exceed 4 characters and route through the existing else branch without touching SimpleDateFormat. Additionally, wrap all four SimpleDateFormat statements (lines 173–176: sdf construction, parse, date_timezone assignment, cisco_timestamp append) in a single try/catch(Exception e) block to catch any remaining unrecognised abbreviations; the catch sets ctx.temp.date_timezone and ctx.event.timezone to ctx._conf?.tz_offset if set, else 'UTC'. This preserves user tz_map precedence, avoids the misidentifying ZoneId.SHORT_IDS fallback, and fixes SGT without breaking existing AEST/ACDT/EDT/CST/METDST cases.

Implementation

  1. Step 1 [default.yml, get_timezone() function, after the 'Z' check at line 152]: Insert a built-in IANA zone-ID map immediately before the 'if (ctx.temp.tz.length() <= 4)' block. Use a Painless def map literal: def builtinTz = ['SGT':'Asia/Singapore','ICT':'Asia/Bangkok','MYT':'Asia/Kuala_Lumpur','PHT':'Asia/Manila','TRT':'Europe/Istanbul','BRT':'America/Sao_Paulo','ART':'America/Argentina/Buenos_Aires','CLT':'America/Santiago','PET':'America/Lima']; if (builtinTz.containsKey(ctx.temp.tz)) { return builtinTz.get(ctx.temp.tz); }. The returned zone IDs all exceed 4 characters, so they bypass the SimpleDateFormat branch and flow through the else branch (ctx.temp.date_timezone = event_timezone), giving DST-correct timestamps rather than fixed offsets.
  2. Step 2 [default.yml, lines 173–176]: Wrap all four SimpleDateFormat statements in a try { ... } catch (Exception e) { ... } block. Place inside the try: (1) SimpleDateFormat sdf = new SimpleDateFormat("z"); (2) sdf.parse(event_timezone); (3) ctx.temp.date_timezone = ZoneId.of(sdf.getTimeZone().getID(), ZoneId.SHORT_IDS).getId(); (4) ctx.temp.cisco_timestamp = ctx.temp.cisco_timestamp + " " + event_timezone; — the last line must be inside try because appending an unresolvable abbreviation and then hitting on_failure resets the whole event. The catch block must NOT branch on e.getClass() (not whitelisted in Painless — only e.getMessage() is). In catch: String fallback = (ctx._conf?.tz_offset != null) ? ctx._conf.tz_offset : 'UTC'; ctx.temp.date_timezone = fallback; ctx.event.timezone = fallback;
  3. Step 3 [test-date-format.log]: Append a new log line exercising the built-in-map happy path for SGT: <190>2361044: sw01: Jan 16 2022 22:11:43.398 SGT: %FOO-6-BAR: Test date format.
  4. Step 4 [test-date-format.log-expected.json]: Add the corresponding expected entry at the end of the 'expected' array. SGT resolves to Asia/Singapore (UTC+8, no DST), so @timestamp = '2022-01-16T22:11:43.398+08:00'. The event.timezone field must be absent (the else branch never sets it; only the no-tz fallback and catch paths do). Match the structure of the existing AWST entry (also +08:00, no event.timezone).
  5. Step 5 [test-date-format-tzoffset.log]: Append one log line using NPT (Nepal Standard Time, UTC+5:45 — absent from the built-in map and not recognised by Java's SimpleDateFormat): <190>2361044: sw01: Jan 16 2022 22:11:43 NPT: %FOO-6-BAR: Test date format. This file's config (test-date-format-tzoffset.log-config.yml) has no tz_offset key, so the catch fallback yields UTC.
  6. Step 6 [test-date-format-tzoffset.log-expected.json]: Add the corresponding expected entry. The catch fires, tz_offset is absent, so @timestamp = '2022-01-16T22:11:43.000Z' and event.timezone = 'UTC'. Match the structure of the no-tz first entry in that file.
  7. Step 7 [changelog.yml]: Add a new top entry: version '1.36.1', type 'bugfix', description 'Fix script_timezone processor failure for unrecognised APAC timezone abbreviations (SGT, ICT and others) by adding a built-in IANA zone-ID lookup and wrapping the SimpleDateFormat call in a try/catch fallback.'

Pipeline changes

  • Modify get_timezone() in script_timezone processor: after the 'Z' check and before length() <= 4, insert a def Map of 9 APAC/regional abbreviation → canonical IANA zone ID entries (SGT, ICT, MYT, PHT, TRT, BRT, ART, CLT, PET); return the IANA ID immediately if matched, so these never reach SimpleDateFormat
  • Wrap the four SimpleDateFormat statements (sdf construction, parse, date_timezone assignment, cisco_timestamp append) in try { ... } catch (Exception e) { ... }; on catch, derive fallback = ctx._conf?.tz_offset ?? 'UTC' and set both ctx.temp.date_timezone and ctx.event.timezone to it; do NOT reference e.getClass() in the catch body

Field / mapping changes

Sanitized error message

Processor 'script' with tag 'script_timezone' in pipeline 'logs-cisco_ios.log-default' failed with message '[on_failure_message]'

Sanitized log (event_sanitized excerpt)

<190>Jun 28 03:00:24 198.51.100.10 SGT:  ROCASRBR-REDACTED 106569598: RP/0/RSP0/CPU0:2026 Jun 28 03:00:24.143 SGT: ipv4_acl_mgr[299]: %ACL-IPV4_ACL-6-IPACCESSLOGDP : access-list oam_egress_nds (100) deny icmp 203.0.113.20 TenGigE0/0/1/0-> 192.0.2.30 (0/0), 300 packets

Reviewer concerns

  • The built-in map covers only 9 abbreviations (SGT, ICT, MYT, PHT, TRT, BRT, ART, CLT, PET); other common ones like AEST, JST, KST, IST remain unhandled and will still require operator-supplied tz_map entries.
  • The fallback sets @timestamp using UTC or the configured offset rather than the device's actual timezone, which means timestamps for unrecognised zones will be silently wrong unless operators notice the cisco_ios_unmapped_timezone tag.
  • event.timezone is only set in the catch branch; for successfully resolved built-in abbreviations the field is not explicitly set, which may be inconsistent with ECS expectations.
  • The NPT test fixture expects @timestamp in UTC (2022-01-16T22:11:43.000Z) — this is correct for the fallback path but reviewers should confirm it matches the intended behaviour (NPT is UTC+5:45, so a UTC timestamp is deliberately incorrect).

Self-review findings

Risk and classification

  • Plan risk level: medium
  • Tags: pipeline, processors, test-fixture, ingest, docs
  • Impact: medium

Links

  • Issue: (no issue number)
  • Issue title: cisco_ios.log [PIPELINE_FIX]: Processor 'script' with tag 'script_timezone' in pipeline 'logs-cisco_io…
  • Pipeline case: 7b2bddd483d30eee

@ie-ops ie-ops added enhancement New feature or request Integration:cisco_ios Cisco IOS source:integration_sentinel The PR was created via the Integration Sentinel pipeline Team:Integration-Experience Security Integrations Integration Experience [elastic/integration-experience] labels Aug 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Elastic Docs Style Checker (Vale)

No issues found on modified lines!


The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale.

@robester0403
robester0403 marked this pull request as ready for review August 14, 2026 17:58
@robester0403
robester0403 requested a review from a team as a code owner August 14, 2026 17:58
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/integration-experience (Team:Integration-Experience)

Comment thread packages/cisco_ios/changelog.yml Outdated
@vinit-chauhan vinit-chauhan self-assigned this Aug 14, 2026
@elastic-vault-github-plugin-prod

Copy link
Copy Markdown
Contributor

✅ All changelog entries have the correct PR link.

ZoneId.of(event_timezone);
ctx._temp_.date_timezone = event_timezone;
} catch (Exception e) {
String fallback = (ctx._conf?.tz_offset != null) ? ctx._conf.tz_offset : 'UTC';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: 🟡 Medium confidence: medium path: packages/cisco_ios/data_stream/log/elasticsearch/ingest_pipeline/default.yml:202

The new catch falls back to _conf.tz_offset, but on the no-_temp_.tz path that is the exact value ZoneId.of() just rejected, so the document still fails the date processor and event.timezone is overwritten with null; only use tz_offset as a fallback when it is not the value that failed, and only set event.timezone when _temp_.tz is non-null.

Details

This catch block completes the fix requested in prior finding 199cb7e30c178460 (validating the long-form branch with ZoneId.of()), and for the _temp_.tz != null case it works as intended. The gap is the other way into this branch.

get_timezone() returns ctx._conf.tz_offset when the log carries no timezone abbreviation (line 170-172). tz_offset is a free-text var (type: text, description 'IANA time zone or time offset (e.g. +0200)'), so an operator value such as UTC+2:00 or one with stray whitespace reaches the else branch (it contains '+' / is longer than 4 chars) and fails ZoneId.of(). In that case:

  1. Line 202 computes fallback as ctx._conf.tz_offset — the very string that just failed validation — so line 203 puts the invalid zone straight back into _temp_.date_timezone and the date_cisco_timestamp processor throws exactly as before. The guard does not actually prevent the failure it was added for.
  2. Line 204 assigns ctx.event.timezone = ctx._temp_.tz, which is null on this path. That discards the timezone value get_timezone() already recorded at line 171, so the document loses event.timezone entirely.

Both are unconditional consequences of the code as written, not a hypothetical. Guarding the fallback also hardens the _temp_.tz != null case, where an invalid tz_offset is likewise copied into date_timezone unvalidated.

Recommendation:

Validate the fallback before using it, and only write event.timezone when there is an abbreviation to record:

          } catch (Exception e) {
            String fallback = 'UTC';
            if (ctx._conf?.tz_offset != null && ctx._conf.tz_offset != event_timezone) {
              try {
                ZoneId.of(ctx._conf.tz_offset);
                fallback = ctx._conf.tz_offset;
              } catch (Exception inner) {
                fallback = 'UTC';
              }
            }
            ctx._temp_.date_timezone = fallback;
            if (ctx._temp_?.tz != null) {
              ctx.event.timezone = ctx._temp_.tz;
            }
            ctx._temp_.tz_unmapped = true;
          }

The same reasoning applies to the SimpleDateFormat catch at lines 187-194, which shares the identical fallback expression.


🤖 AI-Generated Review | Vera Review Bot - v0.2.6 | 📚 Knowledge base: integration-skills

⚠️ Automated review — verify suggestions before applying.

@vera-review-bot

Copy link
Copy Markdown

Review summary

Issues found across the latest commits 4b53fa7 — 1 medium, 1 low
  • 🟡 The new catch falls back to _conf.tz_offset, but on the no-_temp_.tz path that is the exact value ZoneId.of() just rejected, so the document still fails the date processor and event.timezone is overwritten with null (link) (Unresolved)

Package-level:

  • 🔵 The _conf.tz_offset half of the new fallback is untested — every new unmapped-timezone fixture resolves to UTC
Issues found across earlier commits 41ea84f — 2 medium
  • 🟡 The new try/catch fallback only protects timezone strings of 4 characters or fewer, so unrecognised abbreviations of 5-7 characters (e.g. METDST) still fail the document (link) (Resolved)
  • 🟡 Changelog links a different PR number (link) (Resolved)

A new commit triggers another review — at most once every 15 minutes. I skip the PR while it's approved or has merge conflicts.

🤖 AI-Generated Review | Vera Review Bot - v0.2.6 | 📚 Knowledge base: integration-skills

⚠️ Automated review — verify suggestions before applying.

@infra-vault-gh-plugin-prod

infra-vault-gh-plugin-prod Bot commented Aug 14, 2026

Copy link
Copy Markdown

💔 Build Failed

Failed CI Steps

History

cc @vinit-chauhan

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

Labels

enhancement New feature or request Integration:cisco_ios Cisco IOS source:integration_sentinel The PR was created via the Integration Sentinel pipeline Team:Integration-Experience Security Integrations Integration Experience [elastic/integration-experience]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants