@red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki 1.2.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bd80b86: Backstage version bump to v1.51.1
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [bbcdc56]
12
+ - Updated dependencies [bd80b86]
13
+ - @red-hat-developer-hub/backstage-plugin-orchestrator-common@3.7.0
14
+ - @red-hat-developer-hub/backstage-plugin-orchestrator-node@1.3.0
15
+
16
+ ## 1.2.6
17
+
18
+ ### Patch Changes
19
+
20
+ - 5338947: Address security concerns
21
+
3
22
  ## 1.2.5
4
23
 
5
24
  ### Patch Changes
package/README.md CHANGED
@@ -8,28 +8,57 @@ Before installing this module, ensure that the Orchestrator backend plugin is in
8
8
 
9
9
  This module also requires a Loki `workflowLogProvider` integration to be configured in your `app-config.yaml`. This will be added to the `orchestrator` section and might look something like this:
10
10
 
11
- ```
11
+ ```yaml
12
12
  orchestrator:
13
13
  workflowLogProvider:
14
14
  loki:
15
+ # Required: absolute http(s) URL, no userinfo, query, or fragment.
16
+ # Trailing slashes are normalized away. In production (NODE_ENV=production),
17
+ # use https unless you set allowInsecureHttp: true (not recommended).
15
18
  baseUrl: http://localhost:3100
19
+ # Required: bearer token for Loki (treat as a secret in real deployments).
16
20
  token: secrettoken
21
+ # Optional: restrict baseUrl hostname (case-insensitive). Prefix with "." for
22
+ # suffix / subdomain match, e.g. ".example.com" allows loki.prod.example.com.
23
+ # allowedHosts:
24
+ # - loki.example.com
25
+ # - .example.com
26
+ # Optional: allow http:// when NODE_ENV is production (default false).
27
+ # allowInsecureHttp: false
28
+ # Optional: max log lines per query (default 100; must not be negative).
29
+ # limit: 100
30
+ # Optional: set false only if baseUrl uses a cert your Node trust store does not know (e.g. self-signed). Prefer fixing CA trust in production.
17
31
  # rejectUnauthorized: false
32
+ # Optional: extra LogQL pipeline fragments appended after the instance id filter.
18
33
  # logPipelineFilters:
19
- # - '| filter1'
20
- # - '|= filter2'
34
+ # - '| label_format foo="bar"'
35
+ # - '| json'
36
+ # Optional: stream selectors; label names must match Prometheus rules; values are validated at startup.
21
37
  # logStreamSelectors:
22
- # - label: 'app'
23
- # value: '=~".+"'
38
+ # - label: app
39
+ # value: '=~"myapp.+"'
24
40
  ```
25
41
 
26
- The `baseUrl` and `token` are required.
42
+ ### Configuration reference
43
+
44
+ All keys live under `orchestrator.workflowLogProvider.loki`.
45
+
46
+ | Key | Required | Description |
47
+ | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `baseUrl` | yes | Base URL of the Loki service; `/loki/api/v1/query_range` is appended. Must be a valid absolute `http:` or `https:` URL with a hostname. Must not include embedded credentials, a query string, or a fragment. |
49
+ | `token` | yes | Bearer token sent to Loki (`Authorization: Bearer …`). Mark as a secret in config (see `@visibility secret` in schema). |
50
+ | `allowedHosts` | no | If set, the hostname from `baseUrl` must match one entry (case-insensitive). A leading `.` on an entry matches that domain and its subdomains (e.g. `.example.com` matches `loki.example.com`). |
51
+ | `allowInsecureHttp` | no | When `true`, allows `http:` for `baseUrl` even when `NODE_ENV` is `production`. Defaults to `false`. Outside production, `http:` is already allowed for local development. |
52
+ | `limit` | no | Maximum number of log lines to request from Loki per query. Defaults to `100`. Must not be negative. |
53
+ | `rejectUnauthorized` | no | TLS verification for outbound calls to Loki. Defaults to `true`. Set to `false` only if you must talk to a host with a certificate Node does not trust (e.g. self-signed); prefer proper CA configuration when possible. |
54
+ | `logPipelineFilters` | no | Array of strings appended to the LogQL pipeline after the instance id line filter. Validated at startup. |
55
+ | `logStreamSelectors` | no | Array of `{ label?, value? }` objects. Each `label` must be a Prometheus-style name (`[a-zA-Z_][a-zA-Z0-9_]*`). Each `value` is a label matcher fragment only (e.g. `="application"` or `=~".+"`), validated at startup. If omitted or empty, defaults equivalent to OpenShift-style `openshift_log_type="application"` still apply in the provider. |
27
56
 
28
- If you're baseURL is using a self-signed certificate, add the `rejectUnauthorized: false` parameter. Note: this should only be used in development.
57
+ **`baseUrl` and `token` are required.** All other keys are optional.
29
58
 
30
- Multiple Log Stream Selectors can be specified in the `logStreamSelectors` section. See the loki docs to learn more about log stream selectors and their values: https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector
59
+ If your `baseUrl` uses a self-signed certificate, you can set `rejectUnauthorized: false`. Use that sparingly and prefer adding the signing CA to the trust store in production.
31
60
 
32
- Multiple Log Pipeline Filters can be specified in the `logPipelineFilters` section. See the loki docs to learn more about the log pipeline filters and their values and usage: https://grafana.com/docs/loki/latest/query/log_queries/#log-pipeline
61
+ For log stream selectors and pipeline stages, see the Loki documentation: [log stream selector](https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector) and [log pipeline](https://grafana.com/docs/loki/latest/query/log_queries/#log-pipeline).
33
62
 
34
63
  ## Installation
35
64
 
package/config.d.ts CHANGED
@@ -24,12 +24,32 @@ export interface Config {
24
24
  /**
25
25
  * Base URL of the Loki service.
26
26
  * /loki/api/v1/query_range will be appended to the baseUrl
27
+ *
28
+ * Must be an absolute http(s) URL without credentials, query, or fragment.
29
+ * In production (NODE_ENV=production), https is required unless allowInsecureHttp is true.
27
30
  */
28
31
  baseUrl: string;
32
+ /**
33
+ * Optional allowlist for the hostname parsed from baseUrl.
34
+ * Entries are matched case-insensitively. If an entry starts with `.`, the hostname must be
35
+ * that suffix or a subdomain (e.g. `.example.com` allows `loki.example.com`).
36
+ */
37
+ allowedHosts?: string[];
38
+ /**
39
+ * When true, allows http:// baseUrl when NODE_ENV is production.
40
+ * Defaults to false; local/dev typically uses NODE_ENV!=production where http is already allowed.
41
+ */
42
+ allowInsecureHttp?: boolean;
29
43
  /**
30
44
  * Auth Token for accessing the loki query url
31
45
  */
46
+ /** @visibility secret */
32
47
  token: string;
48
+ /**
49
+ * Limit the number of logs to fetch.
50
+ * Defaults to 100. Must not be negative if set.
51
+ */
52
+ limit?: number;
33
53
  /**
34
54
  * Set to false if the baseUrl has a self-signed certificate
35
55
  * defaults to true
@@ -40,10 +60,13 @@ export interface Config {
40
60
  // new values will be appened
41
61
  logPipelineFilters?: Array<string>;
42
62
  logStreamSelectors?: Array<{
43
- // label is the selector, something like 'app' or 'service_name', etc...
44
- label: string;
45
- // value is the label matching operator, so something like: '=~".+"'
46
- value: string;
63
+ /** Prometheus-style name: [a-zA-Z_][a-zA-Z0-9_]* */
64
+ label?: string;
65
+ /**
66
+ * Label matcher only, e.g. `="application"` or `=~".+"` (quoted; regex may use backticks).
67
+ * Validated at startup.
68
+ */
69
+ value?: string;
47
70
  }>;
48
71
  };
49
72
  };
@@ -1,26 +1,45 @@
1
1
  'use strict';
2
2
 
3
+ var errors = require('@backstage/errors');
3
4
  var luxon = require('luxon');
4
5
  var undici = require('undici');
6
+ var helpers = require('./helpers.cjs.js');
5
7
 
8
+ const LOKI_CONFIG_PATH = "orchestrator.workflowLogProvider.loki";
6
9
  class LokiProvider {
7
10
  baseURL;
8
11
  token;
9
12
  selectors;
10
13
  rejectUnauthorized;
11
14
  logPipelineFilters;
15
+ limit;
16
+ agent;
12
17
  constructor(config) {
13
- this.baseURL = config.getString("baseUrl");
18
+ this.baseURL = helpers.parseAndValidateLokiBaseUrl({
19
+ rawBaseUrl: config.getString("baseUrl"),
20
+ allowedHosts: config.getOptionalStringArray("allowedHosts"),
21
+ allowInsecureHttp: config.getOptionalBoolean("allowInsecureHttp")
22
+ });
14
23
  this.token = config.getString("token");
15
24
  this.rejectUnauthorized = config.getOptionalBoolean("rejectUnauthorized") === false ? false : true;
16
- this.selectors = config.getOptional("logStreamSelectors") || [];
17
- this.logPipelineFilters = config.getOptional("logPipelineFilters") || [];
18
- const agent = new undici.Agent({
25
+ this.selectors = helpers.parseAndValidateLogStreamSelectors(
26
+ config,
27
+ `${LOKI_CONFIG_PATH}.logStreamSelectors`
28
+ );
29
+ this.logPipelineFilters = helpers.parseAndValidateLogPipelineFilters(
30
+ config,
31
+ `${LOKI_CONFIG_PATH}.logPipelineFilters`
32
+ );
33
+ const limitOpt = config.getOptionalNumber("limit");
34
+ if (limitOpt !== void 0 && limitOpt < 0) {
35
+ throw new errors.InputError(`${LOKI_CONFIG_PATH}.limit must not be negative`);
36
+ }
37
+ this.limit = limitOpt ?? 100;
38
+ this.agent = new undici.Agent({
19
39
  connect: {
20
40
  rejectUnauthorized: this.rejectUnauthorized
21
41
  }
22
42
  });
23
- undici.setGlobalDispatcher(agent);
24
43
  }
25
44
  getBaseURL() {
26
45
  return this.baseURL;
@@ -38,6 +57,8 @@ class LokiProvider {
38
57
  return this.rejectUnauthorized;
39
58
  }
40
59
  async fetchWorkflowLogsByInstance(instance) {
60
+ helpers.assertSafeWorkflowInstanceIdForLineFilter(instance.id);
61
+ const escapedInstanceId = helpers.escapeLogQlDoubleQuotedLineLiteral(instance.id);
41
62
  const startTime = luxon.DateTime.fromISO(instance.start, {
42
63
  setZone: true
43
64
  }).minus({ minutes: 5 }).toISO();
@@ -47,13 +68,11 @@ class LokiProvider {
47
68
  if (this.selectors.length < 1) {
48
69
  streamSelector = 'openshift_log_type="application"';
49
70
  } else {
50
- this.selectors.forEach(
51
- (entry, index, arr) => {
52
- streamSelector += `${entry.label || "openshift_log_type"}${entry.value || '=~".+"'}${index !== arr.length - 1 ? "," : ""}`;
53
- }
54
- );
71
+ streamSelector = this.selectors.map(
72
+ (entry, index) => `${entry.label}${entry.value}${index === this.selectors.length - 1 ? "" : ","}`
73
+ ).join("");
55
74
  }
56
- let logPipelineFilter = `|="${instance.id}"`;
75
+ let logPipelineFilter = `|="${escapedInstanceId}"`;
57
76
  if (this.logPipelineFilters.length > 0) {
58
77
  this.logPipelineFilters.forEach((element) => {
59
78
  logPipelineFilter += ` ${element}`;
@@ -62,37 +81,37 @@ class LokiProvider {
62
81
  const params = new URLSearchParams({
63
82
  query: `{${streamSelector}} ${logPipelineFilter}`,
64
83
  start: startTime,
65
- end: endTime
84
+ end: endTime,
85
+ limit: this.limit.toString()
66
86
  });
67
87
  const urlToFetch = `${this.baseURL}${lokiApiEndpoint}?${params.toString()}`;
68
88
  let allResults;
69
89
  try {
70
- const response = await fetch(urlToFetch, {
90
+ const response = await undici.fetch(urlToFetch, {
91
+ dispatcher: this.agent,
71
92
  headers: {
72
93
  Authorization: `Bearer ${this.token}`,
73
94
  "Content-Type": "application/json"
74
95
  }
75
96
  });
76
97
  if (!response.ok) {
77
- throw new Error(await response.text());
98
+ throw await errors.ResponseError.fromResponse(response);
78
99
  }
79
100
  const jsonResponse = await response.json();
80
- allResults = jsonResponse.data.result.flatMap((val) => {
81
- return val.values;
82
- }).map((val) => {
83
- return {
84
- id: val[0],
85
- log: val[1]
86
- };
87
- });
101
+ allResults = jsonResponse.data.result.flatMap((entry) => entry.values).map(([id, log]) => ({ id, log }));
88
102
  } catch (error) {
89
- throw new Error(`Problem fetching loki logs: ${error.message}`);
103
+ errors.assertError(error);
104
+ throw new errors.ServiceUnavailableError(
105
+ `Problem fetching loki logs: ${error.message}`,
106
+ error
107
+ );
90
108
  }
109
+ allResults.sort(
110
+ (a, b) => Number(a.id) - Number(b.id)
111
+ );
91
112
  const workflowLogsResponse = {
92
113
  instanceId: instance.id,
93
- logs: allResults.sort((a, b) => {
94
- return Number(a.id) - Number(b.id);
95
- })
114
+ logs: allResults
96
115
  };
97
116
  return workflowLogsResponse;
98
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LokiProvider.cjs.js","sources":["../../src/workflowLogsProviders/LokiProvider.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport { DateTime } from 'luxon';\nimport {\n ProcessInstanceDTO,\n WorkflowLogsResponse,\n} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common';\nimport { WorkflowLogProvider } from '@red-hat-developer-hub/backstage-plugin-orchestrator-node';\n\nimport { Agent, setGlobalDispatcher } from 'undici';\n\nexport class LokiProvider implements WorkflowLogProvider {\n private readonly baseURL: string;\n private readonly token: string;\n private readonly selectors: any;\n private readonly rejectUnauthorized: boolean;\n private readonly logPipelineFilters: any;\n private constructor(config: Config) {\n this.baseURL = config.getString('baseUrl');\n this.token = config.getString('token');\n // Only should be false if specified, undefined here should be true\n this.rejectUnauthorized =\n config.getOptionalBoolean('rejectUnauthorized') === false ? false : true;\n this.selectors = config.getOptional('logStreamSelectors') || [];\n this.logPipelineFilters = config.getOptional('logPipelineFilters') || [];\n\n const agent = new Agent({\n connect: {\n rejectUnauthorized: this.rejectUnauthorized,\n },\n });\n setGlobalDispatcher(agent);\n }\n getBaseURL(): string {\n return this.baseURL;\n }\n\n getProviderId() {\n return 'loki';\n }\n\n getSelectors() {\n return this.selectors;\n }\n\n getToken(): string {\n return this.token;\n }\n\n getRejectUnauthorized(): boolean {\n return this.rejectUnauthorized;\n }\n\n async fetchWorkflowLogsByInstance(\n instance: ProcessInstanceDTO,\n ): Promise<WorkflowLogsResponse> {\n // Because of timing issues, subtract 5 mintues from the start and add 5 minutes to the end\n const startTime = DateTime.fromISO(instance.start as string, {\n setZone: true,\n })\n .minus({ minutes: 5 })\n .toISO();\n // Loki queries time range can't exceeds the limit (query length: 959h37m33.575s, limit: 30d1h)\n // If there is no end date specified, then just add 29 days to the start date\n // Assume that if there is an end date the range between start and end isn't more than 30 days\n const endTime = instance.end\n ? DateTime.fromISO(instance.end as string, { setZone: true })\n .plus({ minutes: 5 })\n .toISO()\n : DateTime.fromISO(instance.start as string, { setZone: true })\n .plus({ days: 29 })\n .toISO();\n const lokiApiEndpoint = '/loki/api/v1/query_range';\n // Query is created with a log stream selector and then a log pipeline for more filtering\n // format looks like this: {stream-selector=expression} | log pipeline/log filter expression\n // The log stream selector part of the query here is defaulting to openshift_log_type=application\n // This is the value used for Openshift Logging\n // This might need to be configurable, based on https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector\n // Log pipeline part looks for the workflow instance id in those logs\n // Create the streamSelector\n let streamSelector: string = '';\n if (this.selectors.length < 1) {\n streamSelector = 'openshift_log_type=\"application\"';\n } else {\n this.selectors.forEach(\n (\n entry: { label: any; value: any },\n index: number,\n arr: string | any[],\n ) => {\n // something about that last comma\n streamSelector += `${entry.label || 'openshift_log_type'}${entry.value || '=~\".+\"'}${index !== arr.length - 1 ? ',' : ''}`;\n },\n );\n }\n let logPipelineFilter: string = `|=\"${instance.id}\"`;\n\n if (this.logPipelineFilters.length > 0) {\n this.logPipelineFilters.forEach((element: any) => {\n logPipelineFilter += ` ${element}`;\n });\n }\n\n const params = new URLSearchParams({\n query: `{${streamSelector}} ${logPipelineFilter}`,\n start: startTime as string,\n end: endTime as string,\n });\n\n const urlToFetch = `${this.baseURL}${lokiApiEndpoint}?${params.toString()}`;\n\n let allResults;\n try {\n const response = await fetch(urlToFetch, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw new Error(await response.text());\n }\n\n const jsonResponse = await response.json();\n\n /**\n Data should look like this\n {\n \"instanceId\": \"efe0490f-6300-453b-bcb6-f47eb7efbb36\",\n \"logs\": [\n {\n id: \"1763129443414066000\",\n log: \"2025-11-14 14:08:52,645 d5932f2cb566 INFO [org.kie.kogito.serverless.workflow.devservices.DevModeServerlessWorkflowLogger:40] (executor-thread-97) Starting workflow 'hello_world' (efe0490f-6300-453b-bcb6-f47eb7efbb36)\"\n },\n ...\n ]\n }\n */\n // flatMap and map the results to an array like this:\n /**\n * {\n * id: '123456',\n * log: '2025-11-14 14:08:52,645 d5932f2cb566 INFO [org.kie.kogito.serverless.workflow.devservices.De....'\n * }\n */\n allResults = jsonResponse.data.result\n .flatMap((val: any[]) => {\n return val.values;\n })\n .map((val: any[]) => {\n return {\n id: val[0],\n log: val[1],\n };\n });\n } catch (error) {\n throw new Error(`Problem fetching loki logs: ${error.message}`);\n }\n\n const workflowLogsResponse: WorkflowLogsResponse = {\n instanceId: instance.id,\n logs: allResults.sort((a: { id: number }, b: { id: number }) => {\n return Number(a.id) - Number(b.id);\n }),\n };\n return workflowLogsResponse;\n }\n\n static fromConfig(config: Config): LokiProvider {\n const lokiConfig = config.getConfig(\n 'orchestrator.workflowLogProvider.loki',\n );\n return new LokiProvider(lokiConfig);\n }\n}\n"],"names":["Agent","setGlobalDispatcher","DateTime"],"mappings":"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"}
1
+ {"version":3,"file":"LokiProvider.cjs.js","sources":["../../src/workflowLogsProviders/LokiProvider.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport {\n assertError,\n ServiceUnavailableError,\n InputError,\n ResponseError,\n} from '@backstage/errors';\nimport { DateTime } from 'luxon';\nimport {\n ProcessInstanceDTO,\n WorkflowLogsResponse,\n} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common';\nimport { WorkflowLogProvider } from '@red-hat-developer-hub/backstage-plugin-orchestrator-node';\n\nimport { Agent, fetch } from 'undici';\nimport {\n assertSafeWorkflowInstanceIdForLineFilter,\n escapeLogQlDoubleQuotedLineLiteral,\n parseAndValidateLogPipelineFilters,\n parseAndValidateLogStreamSelectors,\n parseAndValidateLokiBaseUrl,\n type ValidatedLogStreamSelector,\n} from './helpers';\n\nconst LOKI_CONFIG_PATH = 'orchestrator.workflowLogProvider.loki';\n\n/** Fields read from Loki `query_range` JSON responses in this provider. */\ninterface LokiQueryRangeJsonBody {\n data: {\n result: Array<{ values: [string, string][] }>;\n };\n}\n\ntype LokiParsedLogLine = { id: string; log: string };\n\nexport class LokiProvider implements WorkflowLogProvider {\n private readonly baseURL: string;\n private readonly token: string;\n private readonly selectors: ValidatedLogStreamSelector[];\n private readonly rejectUnauthorized: boolean;\n private readonly logPipelineFilters: string[];\n private readonly limit: number;\n private readonly agent: Agent;\n private constructor(config: Config) {\n this.baseURL = parseAndValidateLokiBaseUrl({\n rawBaseUrl: config.getString('baseUrl'),\n allowedHosts: config.getOptionalStringArray('allowedHosts'),\n allowInsecureHttp: config.getOptionalBoolean('allowInsecureHttp'),\n });\n this.token = config.getString('token');\n // Only should be false if specified, undefined here should be true\n this.rejectUnauthorized =\n config.getOptionalBoolean('rejectUnauthorized') === false ? false : true;\n this.selectors = parseAndValidateLogStreamSelectors(\n config,\n `${LOKI_CONFIG_PATH}.logStreamSelectors`,\n );\n this.logPipelineFilters = parseAndValidateLogPipelineFilters(\n config,\n `${LOKI_CONFIG_PATH}.logPipelineFilters`,\n );\n const limitOpt = config.getOptionalNumber('limit');\n if (limitOpt !== undefined && limitOpt < 0) {\n throw new InputError(`${LOKI_CONFIG_PATH}.limit must not be negative`);\n }\n this.limit = limitOpt ?? 100;\n this.agent = new Agent({\n connect: {\n rejectUnauthorized: this.rejectUnauthorized,\n },\n });\n }\n getBaseURL(): string {\n return this.baseURL;\n }\n\n getProviderId() {\n return 'loki';\n }\n\n getSelectors() {\n return this.selectors;\n }\n\n getToken(): string {\n return this.token;\n }\n\n getRejectUnauthorized(): boolean {\n return this.rejectUnauthorized;\n }\n\n async fetchWorkflowLogsByInstance(\n instance: ProcessInstanceDTO,\n ): Promise<WorkflowLogsResponse> {\n assertSafeWorkflowInstanceIdForLineFilter(instance.id);\n const escapedInstanceId = escapeLogQlDoubleQuotedLineLiteral(instance.id);\n\n // Because of timing issues, subtract 5 mintues from the start and add 5 minutes to the end\n const startTime = DateTime.fromISO(instance.start as string, {\n setZone: true,\n })\n .minus({ minutes: 5 })\n .toISO();\n // Loki queries time range can't exceeds the limit (query length: 959h37m33.575s, limit: 30d1h)\n // If there is no end date specified, then just add 29 days to the start date\n // Assume that if there is an end date the range between start and end isn't more than 30 days\n const endTime = instance.end\n ? DateTime.fromISO(instance.end as string, { setZone: true })\n .plus({ minutes: 5 })\n .toISO()\n : DateTime.fromISO(instance.start as string, { setZone: true })\n .plus({ days: 29 })\n .toISO();\n const lokiApiEndpoint = '/loki/api/v1/query_range';\n // Query is created with a log stream selector and then a log pipeline for more filtering\n // format looks like this: {stream-selector=expression} | log pipeline/log filter expression\n // The log stream selector part of the query here is defaulting to openshift_log_type=application\n // This is the value used for Openshift Logging\n // This might need to be configurable, based on https://grafana.com/docs/loki/latest/query/log_queries/#log-stream-selector\n // Log pipeline part looks for the workflow instance id in those logs\n // Create the streamSelector\n let streamSelector: string = '';\n if (this.selectors.length < 1) {\n streamSelector = 'openshift_log_type=\"application\"';\n } else {\n streamSelector = this.selectors\n .map(\n (entry, index) =>\n `${entry.label}${entry.value}${\n index === this.selectors.length - 1 ? '' : ','\n }`,\n )\n .join('');\n }\n let logPipelineFilter: string = `|=\"${escapedInstanceId}\"`;\n\n if (this.logPipelineFilters.length > 0) {\n this.logPipelineFilters.forEach(element => {\n logPipelineFilter += ` ${element}`;\n });\n }\n\n const params = new URLSearchParams({\n query: `{${streamSelector}} ${logPipelineFilter}`,\n start: startTime as string,\n end: endTime as string,\n limit: this.limit.toString(),\n });\n\n const urlToFetch = `${this.baseURL}${lokiApiEndpoint}?${params.toString()}`;\n\n let allResults!: LokiParsedLogLine[];\n try {\n const response = await fetch(urlToFetch, {\n dispatcher: this.agent,\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const jsonResponse = (await response.json()) as LokiQueryRangeJsonBody;\n\n /**\n Data should look like this\n {\n \"instanceId\": \"efe0490f-6300-453b-bcb6-f47eb7efbb36\",\n \"logs\": [\n {\n id: \"1763129443414066000\",\n log: \"2025-11-14 14:08:52,645 d5932f2cb566 INFO [org.kie.kogito.serverless.workflow.devservices.DevModeServerlessWorkflowLogger:40] (executor-thread-97) Starting workflow 'hello_world' (efe0490f-6300-453b-bcb6-f47eb7efbb36)\"\n },\n ...\n ]\n }\n */\n // flatMap and map the results to an array like this:\n /**\n * {\n * id: '123456',\n * log: '2025-11-14 14:08:52,645 d5932f2cb566 INFO [org.kie.kogito.serverless.workflow.devservices.De....'\n * }\n */\n allResults = jsonResponse.data.result\n .flatMap(entry => entry.values)\n .map(([id, log]) => ({ id, log }));\n } catch (error: unknown) {\n assertError(error);\n throw new ServiceUnavailableError(\n `Problem fetching loki logs: ${error.message}`,\n error,\n );\n }\n\n allResults.sort(\n (a: LokiParsedLogLine, b: LokiParsedLogLine) =>\n Number(a.id) - Number(b.id),\n );\n\n const workflowLogsResponse: WorkflowLogsResponse = {\n instanceId: instance.id,\n logs: allResults,\n };\n return workflowLogsResponse;\n }\n\n static fromConfig(config: Config): LokiProvider {\n const lokiConfig = config.getConfig(\n 'orchestrator.workflowLogProvider.loki',\n );\n return new LokiProvider(lokiConfig);\n }\n}\n"],"names":["parseAndValidateLokiBaseUrl","parseAndValidateLogStreamSelectors","parseAndValidateLogPipelineFilters","InputError","Agent","assertSafeWorkflowInstanceIdForLineFilter","escapeLogQlDoubleQuotedLineLiteral","DateTime","fetch","ResponseError","assertError","ServiceUnavailableError"],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ function hostnameMatchesAllowedHosts(hostname, allowedHosts) {
6
+ const hostLower = hostname.toLowerCase();
7
+ return allowedHosts.some((pattern) => {
8
+ const p = pattern.trim().toLowerCase();
9
+ if (!p) {
10
+ return false;
11
+ }
12
+ if (p.startsWith(".")) {
13
+ const suffix = p.slice(1);
14
+ return hostLower === suffix || hostLower.endsWith(`.${suffix}`);
15
+ }
16
+ return hostLower === p;
17
+ });
18
+ }
19
+ function parseAndValidateLokiBaseUrl(options) {
20
+ const trimmed = options.rawBaseUrl.trim();
21
+ if (!trimmed) {
22
+ throw new errors.InputError(
23
+ "orchestrator.workflowLogProvider.loki.baseUrl must not be empty"
24
+ );
25
+ }
26
+ let parsed;
27
+ try {
28
+ parsed = new URL(trimmed);
29
+ } catch {
30
+ throw new errors.InputError(
31
+ `orchestrator.workflowLogProvider.loki.baseUrl must be a valid absolute URL, got "${trimmed}"`
32
+ );
33
+ }
34
+ if (parsed.username || parsed.password) {
35
+ throw new errors.InputError(
36
+ "orchestrator.workflowLogProvider.loki.baseUrl must not include embedded credentials"
37
+ );
38
+ }
39
+ if (parsed.search || parsed.hash) {
40
+ throw new errors.InputError(
41
+ "orchestrator.workflowLogProvider.loki.baseUrl must not include a query or fragment"
42
+ );
43
+ }
44
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
45
+ throw new errors.InputError(
46
+ `orchestrator.workflowLogProvider.loki.baseUrl must use http: or https:, got "${parsed.protocol}"`
47
+ );
48
+ }
49
+ if (!parsed.hostname) {
50
+ throw new errors.InputError(
51
+ "orchestrator.workflowLogProvider.loki.baseUrl must include a hostname"
52
+ );
53
+ }
54
+ const isProduction = process.env.NODE_ENV === "production";
55
+ if (isProduction && parsed.protocol === "http:" && options.allowInsecureHttp !== true) {
56
+ throw new errors.InputError(
57
+ "orchestrator.workflowLogProvider.loki.baseUrl must use https in production (set allowInsecureHttp to true only if you explicitly require http)"
58
+ );
59
+ }
60
+ const hosts = options.allowedHosts?.map((h) => h.trim()).filter(Boolean) ?? [];
61
+ if (hosts.length > 0 && !hostnameMatchesAllowedHosts(parsed.hostname, hosts)) {
62
+ throw new errors.InputError(
63
+ `orchestrator.workflowLogProvider.loki.baseUrl hostname "${parsed.hostname}" is not allowed by allowedHosts`
64
+ );
65
+ }
66
+ let pathname = parsed.pathname;
67
+ while (pathname.endsWith("/")) {
68
+ pathname = pathname.slice(0, -1);
69
+ }
70
+ return pathname ? `${parsed.origin}${pathname}` : parsed.origin;
71
+ }
72
+ const LOKI_LABEL_NAME_PATTERN = /^[a-zA-Z_]\w*$/;
73
+ const PROMETHEUS_LABEL_NAME_RULE_DESC = String.raw`[a-zA-Z_]\w*`;
74
+ const LOG_STREAM_SELECTOR_VALUE_PATTERNS = [
75
+ /^=(?:"(?:[^"\\\r\n]|\\.)*")$/,
76
+ /^!=(?:"(?:[^"\\\r\n]|\\.)*")$/,
77
+ /^=~(?:"(?:[^"\\\r\n]|\\.)*"|`[^`\r\n]*`)$/,
78
+ /^!~(?:"(?:[^"\\\r\n]|\\.)*"|`[^`\r\n]*`)$/
79
+ ];
80
+ function assertValidLokiLabelName(label, context) {
81
+ if (!LOKI_LABEL_NAME_PATTERN.test(label)) {
82
+ throw new Error(
83
+ `${context}: label must match Prometheus label name rules ${PROMETHEUS_LABEL_NAME_RULE_DESC} (got "${label}")`
84
+ );
85
+ }
86
+ }
87
+ function assertValidLogStreamSelectorValue(value, context) {
88
+ if (!LOG_STREAM_SELECTOR_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
89
+ throw new Error(
90
+ `${context}: value must be a LogQL label matcher (e.g. ="literal", !="...", =~"re", or =~\`re\`) with no raw line breaks outside escapes`
91
+ );
92
+ }
93
+ }
94
+ function parseAndValidateLogStreamSelectors(lokiConfig, configKeyPath) {
95
+ const entries = lokiConfig.getOptionalConfigArray("logStreamSelectors");
96
+ if (!entries?.length) {
97
+ return [];
98
+ }
99
+ return entries.map((entry, i) => {
100
+ const label = entry.getOptionalString("label") ?? "openshift_log_type";
101
+ const value = entry.getOptionalString("value") ?? '="application"';
102
+ const ctxBase = `${configKeyPath}[${i}]`;
103
+ assertValidLokiLabelName(label, ctxBase);
104
+ assertValidLogStreamSelectorValue(value, ctxBase);
105
+ return { label, value };
106
+ });
107
+ }
108
+ function parseAndValidateLogPipelineFilters(lokiConfig, configKeyPath) {
109
+ const filters = lokiConfig.getOptionalStringArray("logPipelineFilters");
110
+ if (!filters?.length) {
111
+ return [];
112
+ }
113
+ return filters.map((raw, i) => {
114
+ const element = raw.trim();
115
+ const ctx = `${configKeyPath}[${i}]`;
116
+ if (!element) {
117
+ throw new errors.InputError(
118
+ `${ctx}: entry must not be empty or whitespace-only`
119
+ );
120
+ }
121
+ if (/[\r\n\u2028\u2029]/.test(element)) {
122
+ throw new errors.InputError(`${ctx}: entry must not contain line breaks`);
123
+ }
124
+ if (element.includes("{")) {
125
+ throw new errors.InputError(`${ctx}: entry must not contain "{"`);
126
+ }
127
+ if (element.includes("}")) {
128
+ throw new errors.InputError(`${ctx}: entry must not contain "}"`);
129
+ }
130
+ return element;
131
+ });
132
+ }
133
+ function workflowInstanceIdHasDisallowedChars(id) {
134
+ for (let i = 0; i < id.length; i++) {
135
+ const c = id.codePointAt(i);
136
+ if (c === void 0) {
137
+ continue;
138
+ }
139
+ if (c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31 || c === 127 || c === 10 || c === 13 || c === 8232 || c === 8233) {
140
+ return true;
141
+ }
142
+ }
143
+ return false;
144
+ }
145
+ function assertSafeWorkflowInstanceIdForLineFilter(id) {
146
+ if (workflowInstanceIdHasDisallowedChars(id)) {
147
+ throw new errors.InputError(
148
+ "Workflow instance id contains characters that are not allowed in Loki line filters"
149
+ );
150
+ }
151
+ }
152
+ function escapeLogQlDoubleQuotedLineLiteral(fragment) {
153
+ return fragment.replaceAll("\\", String.raw`\\`).replaceAll('"', String.raw`\"`);
154
+ }
155
+
156
+ exports.assertSafeWorkflowInstanceIdForLineFilter = assertSafeWorkflowInstanceIdForLineFilter;
157
+ exports.escapeLogQlDoubleQuotedLineLiteral = escapeLogQlDoubleQuotedLineLiteral;
158
+ exports.hostnameMatchesAllowedHosts = hostnameMatchesAllowedHosts;
159
+ exports.parseAndValidateLogPipelineFilters = parseAndValidateLogPipelineFilters;
160
+ exports.parseAndValidateLogStreamSelectors = parseAndValidateLogStreamSelectors;
161
+ exports.parseAndValidateLokiBaseUrl = parseAndValidateLokiBaseUrl;
162
+ //# sourceMappingURL=helpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../src/workflowLogsProviders/helpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\n\n/**\n * Returns whether `hostname` matches any entry in `allowedHosts` (case-insensitive).\n * Empty or whitespace-only patterns are ignored.\n */\nexport function hostnameMatchesAllowedHosts(\n hostname: string,\n allowedHosts: string[],\n): boolean {\n const hostLower = hostname.toLowerCase();\n return allowedHosts.some(pattern => {\n const p = pattern.trim().toLowerCase();\n if (!p) {\n return false;\n }\n if (p.startsWith('.')) {\n const suffix = p.slice(1);\n return hostLower === suffix || hostLower.endsWith(`.${suffix}`);\n }\n return hostLower === p;\n });\n}\n\n/**\n * Parses baseUrl, applies scheme/host policy, and returns a normalized origin[+path] base\n * (no trailing slash) for appending Loki API paths.\n */\nexport function parseAndValidateLokiBaseUrl(options: {\n rawBaseUrl: string;\n allowedHosts?: string[];\n allowInsecureHttp?: boolean;\n}): string {\n const trimmed = options.rawBaseUrl.trim();\n if (!trimmed) {\n throw new InputError(\n 'orchestrator.workflowLogProvider.loki.baseUrl must not be empty',\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new InputError(\n `orchestrator.workflowLogProvider.loki.baseUrl must be a valid absolute URL, got \"${trimmed}\"`,\n );\n }\n\n if (parsed.username || parsed.password) {\n throw new InputError(\n 'orchestrator.workflowLogProvider.loki.baseUrl must not include embedded credentials',\n );\n }\n\n if (parsed.search || parsed.hash) {\n throw new InputError(\n 'orchestrator.workflowLogProvider.loki.baseUrl must not include a query or fragment',\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new InputError(\n `orchestrator.workflowLogProvider.loki.baseUrl must use http: or https:, got \"${parsed.protocol}\"`,\n );\n }\n\n if (!parsed.hostname) {\n throw new InputError(\n 'orchestrator.workflowLogProvider.loki.baseUrl must include a hostname',\n );\n }\n\n const isProduction = process.env.NODE_ENV === 'production';\n if (\n isProduction &&\n parsed.protocol === 'http:' &&\n options.allowInsecureHttp !== true\n ) {\n throw new InputError(\n 'orchestrator.workflowLogProvider.loki.baseUrl must use https in production (set allowInsecureHttp to true only if you explicitly require http)',\n );\n }\n\n const hosts = options.allowedHosts?.map(h => h.trim()).filter(Boolean) ?? [];\n if (\n hosts.length > 0 &&\n !hostnameMatchesAllowedHosts(parsed.hostname, hosts)\n ) {\n throw new InputError(\n `orchestrator.workflowLogProvider.loki.baseUrl hostname \"${parsed.hostname}\" is not allowed by allowedHosts`,\n );\n }\n\n let pathname = parsed.pathname;\n while (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n return pathname ? `${parsed.origin}${pathname}` : parsed.origin;\n}\n\n/** Prometheus / Loki stream label names: `[a-zA-Z_]\\w*` (ASCII `\\w` only; no `/u` flag). */\nconst LOKI_LABEL_NAME_PATTERN = /^[a-zA-Z_]\\w*$/;\nconst PROMETHEUS_LABEL_NAME_RULE_DESC = String.raw`[a-zA-Z_]\\w*`;\n\n/**\n * Label matcher fragment after the label name: `=\"...\"`, `!=`, regex with `\"` or `` ` ``.\n * Requires properly closed quotes and escapes so the fragment cannot break out of `{...}`.\n */\nconst LOG_STREAM_SELECTOR_VALUE_PATTERNS: RegExp[] = [\n /^=(?:\"(?:[^\"\\\\\\r\\n]|\\\\.)*\")$/,\n /^!=(?:\"(?:[^\"\\\\\\r\\n]|\\\\.)*\")$/,\n /^=~(?:\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"|`[^`\\r\\n]*`)$/,\n /^!~(?:\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"|`[^`\\r\\n]*`)$/,\n];\n\nexport interface ValidatedLogStreamSelector {\n label: string;\n value: string;\n}\n\nfunction assertValidLokiLabelName(label: string, context: string): void {\n if (!LOKI_LABEL_NAME_PATTERN.test(label)) {\n throw new Error(\n `${context}: label must match Prometheus label name rules ${PROMETHEUS_LABEL_NAME_RULE_DESC} (got \"${label}\")`,\n );\n }\n}\n\nfunction assertValidLogStreamSelectorValue(\n value: string,\n context: string,\n): void {\n if (\n !LOG_STREAM_SELECTOR_VALUE_PATTERNS.some(pattern => pattern.test(value))\n ) {\n throw new Error(\n `${context}: value must be a LogQL label matcher (e.g. =\"literal\", !=\"...\", =~\"re\", or =~\\`re\\`) with no raw line breaks outside escapes`,\n );\n }\n}\n\n/**\n * Reads and validates `logStreamSelectors` at startup.\n */\nexport function parseAndValidateLogStreamSelectors(\n lokiConfig: Config,\n configKeyPath: string,\n): ValidatedLogStreamSelector[] {\n const entries = lokiConfig.getOptionalConfigArray('logStreamSelectors');\n if (!entries?.length) {\n return [];\n }\n return entries.map((entry, i) => {\n const label = entry.getOptionalString('label') ?? 'openshift_log_type';\n const value = entry.getOptionalString('value') ?? '=\"application\"';\n const ctxBase = `${configKeyPath}[${i}]`;\n assertValidLokiLabelName(label, ctxBase);\n assertValidLogStreamSelectorValue(value, ctxBase);\n return { label, value };\n });\n}\n\n/**\n * Reads and validates `logPipelineFilters` at startup.\n */\nexport function parseAndValidateLogPipelineFilters(\n lokiConfig: Config,\n configKeyPath: string,\n): string[] {\n const filters = lokiConfig.getOptionalStringArray('logPipelineFilters');\n if (!filters?.length) {\n return [];\n }\n return filters.map((raw, i) => {\n const element = raw.trim();\n const ctx = `${configKeyPath}[${i}]`;\n if (!element) {\n throw new InputError(\n `${ctx}: entry must not be empty or whitespace-only`,\n );\n }\n if (/[\\r\\n\\u2028\\u2029]/.test(element)) {\n throw new InputError(`${ctx}: entry must not contain line breaks`);\n }\n if (element.includes('{')) {\n throw new InputError(`${ctx}: entry must not contain \"{\"`);\n }\n if (element.includes('}')) {\n throw new InputError(`${ctx}: entry must not contain \"}\"`);\n }\n return element;\n });\n}\n\nfunction workflowInstanceIdHasDisallowedChars(id: string): boolean {\n for (let i = 0; i < id.length; i++) {\n const c = id.codePointAt(i);\n if (c === undefined) {\n continue;\n }\n if (\n (c >= 0x00 && c <= 0x08) ||\n c === 0x0b ||\n c === 0x0c ||\n (c >= 0x0e && c <= 0x1f) ||\n c === 0x7f ||\n c === 0x0a ||\n c === 0x0d ||\n c === 0x2028 ||\n c === 0x2029\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Rejects instance ids that cannot be safely embedded in a LogQL line filter.\n */\nexport function assertSafeWorkflowInstanceIdForLineFilter(id: string): void {\n if (workflowInstanceIdHasDisallowedChars(id)) {\n throw new InputError(\n 'Workflow instance id contains characters that are not allowed in Loki line filters',\n );\n }\n}\n\n/**\n * Escapes a string for use inside LogQL double-quoted line filter literals (`|=\"...\"`).\n */\nexport function escapeLogQlDoubleQuotedLineLiteral(fragment: string): string {\n return fragment\n .replaceAll('\\\\', String.raw`\\\\`)\n .replaceAll('\"', String.raw`\\\"`);\n}\n"],"names":["InputError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The loki backend module for the orchestrator plugin.",
6
6
  "main": "./dist/index.cjs.js",
@@ -45,16 +45,17 @@
45
45
  "prettier:fix": "prettier --ignore-unknown --write ."
46
46
  },
47
47
  "dependencies": {
48
- "@backstage/backend-plugin-api": "^1.8.0",
49
- "@red-hat-developer-hub/backstage-plugin-orchestrator-common": "^3.6.4",
50
- "@red-hat-developer-hub/backstage-plugin-orchestrator-node": "^1.2.4",
48
+ "@backstage/backend-plugin-api": "^1.9.1",
49
+ "@backstage/errors": "^1.3.1",
50
+ "@red-hat-developer-hub/backstage-plugin-orchestrator-common": "^3.7.0",
51
+ "@red-hat-developer-hub/backstage-plugin-orchestrator-node": "^1.3.0",
51
52
  "luxon": "^3.7.2",
52
53
  "undici": "^7.24.0"
53
54
  },
54
55
  "devDependencies": {
55
- "@backstage/backend-test-utils": "^1.11.1",
56
- "@backstage/cli": "^0.36.0",
57
- "@backstage/config": "^1.3.6"
56
+ "@backstage/backend-test-utils": "^1.11.3",
57
+ "@backstage/cli": "^0.36.2",
58
+ "@backstage/config": "^1.3.8"
58
59
  },
59
60
  "files": [
60
61
  "dist",