pepr 0.46.2-nightly.0 → 0.46.2-nightly.2

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.
Files changed (65) hide show
  1. package/dist/cli.js +79 -23
  2. package/dist/controller.js +1 -1
  3. package/dist/lib.js +124 -38
  4. package/dist/lib.js.map +2 -2
  5. package/dist/src/cli/init/templates.d.ts +2 -2
  6. package/dist/src/cli/init/templates.d.ts.map +1 -1
  7. package/dist/src/lib/assets/assets.d.ts.map +1 -1
  8. package/dist/src/lib/assets/defaultTestObjects.d.ts.map +1 -1
  9. package/dist/src/lib/assets/deploy.d.ts.map +1 -1
  10. package/dist/src/lib/assets/index.d.ts.map +1 -1
  11. package/dist/src/lib/assets/pods.d.ts.map +1 -1
  12. package/dist/src/lib/assets/webhooks.d.ts.map +1 -1
  13. package/dist/src/lib/assets/yaml/generateAllYaml.d.ts.map +1 -1
  14. package/dist/src/lib/controller/index.d.ts.map +1 -1
  15. package/dist/src/lib/core/capability.d.ts.map +1 -1
  16. package/dist/src/lib/core/module.d.ts.map +1 -1
  17. package/dist/src/lib/core/storage.d.ts.map +1 -1
  18. package/dist/src/lib/deploymentChecks.d.ts.map +1 -1
  19. package/dist/src/lib/filter/adjudicators/admissionRequest.d.ts.map +1 -1
  20. package/dist/src/lib/filter/adjudicators/binding.d.ts.map +1 -1
  21. package/dist/src/lib/filter/adjudicators/kubernetesObject.d.ts.map +1 -1
  22. package/dist/src/lib/filter/adjudicators/mismatch.d.ts.map +1 -1
  23. package/dist/src/lib/filter/adjudicators/postCollection.d.ts.map +1 -1
  24. package/dist/src/lib/filter/filter.d.ts.map +1 -1
  25. package/dist/src/lib/helpers.d.ts.map +1 -1
  26. package/dist/src/lib/included-files.d.ts.map +1 -1
  27. package/dist/src/lib/processors/decode-utils.d.ts.map +1 -1
  28. package/dist/src/lib/processors/mutate-processor.d.ts.map +1 -1
  29. package/dist/src/lib/processors/validate-processor.d.ts.map +1 -1
  30. package/dist/src/lib/processors/watch-processor.d.ts.map +1 -1
  31. package/dist/src/lib/telemetry/metrics.d.ts.map +1 -1
  32. package/dist/src/lib/telemetry/webhookTimeouts.d.ts.map +1 -1
  33. package/package.json +4 -5
  34. package/src/lib/assets/assets.ts +46 -11
  35. package/src/lib/assets/defaultTestObjects.ts +13 -2
  36. package/src/lib/assets/deploy.ts +25 -5
  37. package/src/lib/assets/index.ts +8 -2
  38. package/src/lib/assets/pods.ts +5 -1
  39. package/src/lib/assets/webhooks.ts +12 -3
  40. package/src/lib/assets/yaml/generateAllYaml.ts +12 -2
  41. package/src/lib/controller/index.ts +9 -3
  42. package/src/lib/core/capability.ts +32 -8
  43. package/src/lib/core/module.ts +5 -1
  44. package/src/lib/core/storage.ts +3 -1
  45. package/src/lib/deploymentChecks.ts +3 -1
  46. package/src/lib/filter/adjudicators/admissionRequest.ts +4 -1
  47. package/src/lib/filter/adjudicators/binding.ts +17 -4
  48. package/src/lib/filter/adjudicators/kubernetesObject.ts +4 -2
  49. package/src/lib/filter/adjudicators/mismatch.ts +25 -6
  50. package/src/lib/filter/adjudicators/postCollection.ts +15 -3
  51. package/src/lib/filter/filter.ts +63 -15
  52. package/src/lib/helpers.ts +36 -10
  53. package/src/lib/included-files.ts +5 -1
  54. package/src/lib/processors/decode-utils.ts +4 -1
  55. package/src/lib/processors/mutate-processor.ts +4 -1
  56. package/src/lib/processors/validate-processor.ts +4 -1
  57. package/src/lib/processors/watch-processor.ts +49 -19
  58. package/src/lib/telemetry/metrics.ts +6 -2
  59. package/src/lib/telemetry/webhookTimeouts.ts +4 -1
  60. package/src/templates/.prettierrc.json +3 -2
  61. package/src/templates/capabilities/hello-pepr.ts +2 -8
  62. package/dist/src/sdk/cosign.d.ts +0 -18
  63. package/dist/src/sdk/cosign.d.ts.map +0 -1
  64. package/src/lib/.prettierrc +0 -14
  65. package/src/sdk/cosign.ts +0 -327
@@ -4,7 +4,13 @@ import Log from "../telemetry/logger";
4
4
  import { Binding } from "../types";
5
5
  import { Capability } from "../core/capability";
6
6
  import { Event } from "../enums";
7
- import { K8s, KubernetesObject, WatchCfg, WatchEvent, GenericClass } from "kubernetes-fluent-client";
7
+ import {
8
+ K8s,
9
+ KubernetesObject,
10
+ WatchCfg,
11
+ WatchEvent,
12
+ GenericClass,
13
+ } from "kubernetes-fluent-client";
8
14
  import { Queue } from "../core/queue";
9
15
  import { WatchPhase, WatcherType } from "kubernetes-fluent-client/dist/fluent/types";
10
16
  import { KubernetesListObject } from "kubernetes-fluent-client/dist/types";
@@ -51,8 +57,12 @@ export function getOrCreateQueue(obj: KubernetesObject): Queue<KubernetesObject>
51
57
 
52
58
  // Watch configuration
53
59
  const watchCfg: WatchCfg = {
54
- resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
55
- resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,
60
+ resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX
61
+ ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10)
62
+ : 5,
63
+ resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS
64
+ ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10)
65
+ : 5,
56
66
  lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS
57
67
  ? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10)
58
68
  : 300,
@@ -79,7 +89,9 @@ export function setupWatch(capabilities: Capability[], ignoredNamespaces?: strin
79
89
  capabilities.map(capability =>
80
90
  capability.bindings
81
91
  .filter(binding => binding.isWatch)
82
- .forEach(bindingElement => runBinding(bindingElement, capability.namespaces, ignoredNamespaces)),
92
+ .forEach(bindingElement =>
93
+ runBinding(bindingElement, capability.namespaces, ignoredNamespaces),
94
+ ),
83
95
  );
84
96
  }
85
97
 
@@ -100,12 +112,20 @@ async function runBinding(
100
112
  // The watch callback is run when an object is received or dequeued
101
113
  Log.debug({ watchCfg }, "Effective WatchConfig");
102
114
 
103
- const watchCallback = async (kubernetesObject: KubernetesObject, phase: WatchPhase): Promise<void> => {
115
+ const watchCallback = async (
116
+ kubernetesObject: KubernetesObject,
117
+ phase: WatchPhase,
118
+ ): Promise<void> => {
104
119
  // First, filter the object based on the phase
105
120
  if (phaseMatch.includes(phase)) {
106
121
  try {
107
122
  // Then, check if the object matches the filter
108
- const filterMatch = filterNoMatchReason(binding, kubernetesObject, capabilityNamespaces, ignoredNamespaces);
123
+ const filterMatch = filterNoMatchReason(
124
+ binding,
125
+ kubernetesObject,
126
+ capabilityNamespaces,
127
+ ignoredNamespaces,
128
+ );
109
129
  if (filterMatch !== "") {
110
130
  Log.debug(filterMatch);
111
131
  return;
@@ -139,7 +159,10 @@ async function runBinding(
139
159
  // [ true, void, undefined ] SHOULD remove finalizer
140
160
  // [ false ] should NOT remove finalizer
141
161
  if (shouldRemoveFinalizer === false) {
142
- Log.debug({ obj: kubernetesObject }, `Skipping removal of finalizer '${peprFinal}' from '${resource}'`);
162
+ Log.debug(
163
+ { obj: kubernetesObject },
164
+ `Skipping removal of finalizer '${peprFinal}' from '${resource}'`,
165
+ );
143
166
  } else {
144
167
  await removeFinalizer(binding, kubernetesObject);
145
168
  }
@@ -147,16 +170,19 @@ async function runBinding(
147
170
  };
148
171
 
149
172
  // Setup the resource watch
150
- const watcher = K8s(binding.model, { ...binding.filters, kindOverride: binding.kind }).Watch(async (obj, phase) => {
151
- Log.debug(obj, `Watch event ${phase} received`);
152
-
153
- if (binding.isQueue) {
154
- const queue = getOrCreateQueue(obj);
155
- await queue.enqueue(obj, phase, watchCallback);
156
- } else {
157
- await watchCallback(obj, phase);
158
- }
159
- }, watchCfg);
173
+ const watcher = K8s(binding.model, { ...binding.filters, kindOverride: binding.kind }).Watch(
174
+ async (obj, phase) => {
175
+ Log.debug(obj, `Watch event ${phase} received`);
176
+
177
+ if (binding.isQueue) {
178
+ const queue = getOrCreateQueue(obj);
179
+ await queue.enqueue(obj, phase, watchCallback);
180
+ } else {
181
+ await watchCallback(obj, phase);
182
+ }
183
+ },
184
+ watchCfg,
185
+ );
160
186
 
161
187
  // Register event handlers
162
188
  registerWatchEventHandlers(watcher, logEvent, metricsCollector);
@@ -214,10 +240,14 @@ export function registerWatchEventHandlers(
214
240
  [WatchEvent.CONNECT]: url => logEvent(WatchEvent.CONNECT, url),
215
241
  [WatchEvent.DATA_ERROR]: err => logEvent(WatchEvent.DATA_ERROR, err.message),
216
242
  [WatchEvent.RECONNECT]: retryCount =>
217
- logEvent(WatchEvent.RECONNECT, `Reconnecting after ${retryCount} attempt${retryCount === 1 ? "" : "s"}`),
243
+ logEvent(
244
+ WatchEvent.RECONNECT,
245
+ `Reconnecting after ${retryCount} attempt${retryCount === 1 ? "" : "s"}`,
246
+ ),
218
247
  [WatchEvent.RECONNECT_PENDING]: () => logEvent(WatchEvent.RECONNECT_PENDING),
219
248
  [WatchEvent.ABORT]: err => logEvent(WatchEvent.ABORT, err.message),
220
- [WatchEvent.OLD_RESOURCE_VERSION]: errMessage => logEvent(WatchEvent.OLD_RESOURCE_VERSION, errMessage),
249
+ [WatchEvent.OLD_RESOURCE_VERSION]: errMessage =>
250
+ logEvent(WatchEvent.OLD_RESOURCE_VERSION, errMessage),
221
251
  [WatchEvent.NETWORK_ERROR]: err => logEvent(WatchEvent.NETWORK_ERROR, err.message),
222
252
  [WatchEvent.LIST_ERROR]: err => logEvent(WatchEvent.LIST_ERROR, err.message),
223
253
  [WatchEvent.LIST]: list => logEvent(WatchEvent.LIST, JSON.stringify(list, undefined, 2)),
@@ -56,7 +56,9 @@ export class MetricsCollector {
56
56
  this.addSummary(this.#metricNames.mutate, "Mutation operation summary");
57
57
  this.addSummary(this.#metricNames.validate, "Validation operation summary");
58
58
  this.addGauge(this.#metricNames.cacheMiss, "Number of cache misses per window", ["window"]);
59
- this.addGauge(this.#metricNames.resyncFailureCount, "Number of failures per resync operation", ["count"]);
59
+ this.addGauge(this.#metricNames.resyncFailureCount, "Number of failures per resync operation", [
60
+ "count",
61
+ ]);
60
62
  }
61
63
 
62
64
  #getMetricName = (name: string): string => `${this.#prefix}_${name}`;
@@ -173,7 +175,9 @@ export class MetricsCollector {
173
175
  if (firstKey !== undefined) {
174
176
  this.#cacheMissWindows.delete(firstKey);
175
177
  }
176
- this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.remove({ window: firstKey });
178
+ this.#gauges
179
+ .get(this.#getMetricName(this.#metricNames.cacheMiss))
180
+ ?.remove({ window: firstKey });
177
181
  }
178
182
  };
179
183
  }
@@ -9,7 +9,10 @@ export class MeasureWebhookTimeout {
9
9
 
10
10
  constructor(webhookType: WebhookType) {
11
11
  this.#webhookType = webhookType;
12
- metricsCollector.addCounter(`${webhookType}_timeouts`, `Number of ${webhookType} webhook timeouts`);
12
+ metricsCollector.addCounter(
13
+ `${webhookType}_timeouts`,
14
+ `Number of ${webhookType} webhook timeouts`,
15
+ );
13
16
  }
14
17
 
15
18
  start(timeout: number = 10): void {
@@ -4,10 +4,11 @@
4
4
  "bracketSpacing": true,
5
5
  "embeddedLanguageFormatting": "auto",
6
6
  "insertPragma": false,
7
- "printWidth": 80,
7
+ "printWidth": 100,
8
8
  "quoteProps": "as-needed",
9
9
  "requirePragma": false,
10
10
  "semi": true,
11
11
  "tabWidth": 2,
12
- "useTabs": false
12
+ "useTabs": false,
13
+ "vueIndentScriptAndStyle": false
13
14
  }
@@ -89,9 +89,7 @@ When(a.ConfigMap)
89
89
  .IsCreated()
90
90
  .WithName("example-1")
91
91
  .Mutate(request => {
92
- request
93
- .SetLabel("pepr", "was-here")
94
- .SetAnnotation("pepr.dev", "annotations-work-too");
92
+ request.SetLabel("pepr", "was-here").SetAnnotation("pepr.dev", "annotations-work-too");
95
93
 
96
94
  // Use the Store to persist data between requests and Pepr controller pods
97
95
  Store.setItem("example-1", "was-here");
@@ -228,11 +226,7 @@ function example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {
228
226
  * Note because the Capability defines namespaces, the namespace specified here must be one of those.
229
227
  * Alternatively, you can remove the namespace from the Capability definition and specify it here.
230
228
  */
231
- When(a.ConfigMap)
232
- .IsCreated()
233
- .InNamespace("pepr-demo-2")
234
- .WithName("example-4a")
235
- .Mutate(example4Cb);
229
+ When(a.ConfigMap).IsCreated().InNamespace("pepr-demo-2").WithName("example-4a").Mutate(example4Cb);
236
230
 
237
231
  /**
238
232
  * ---------------------------------------------------------------------------------------------------
@@ -1,18 +0,0 @@
1
- export declare enum MediaTypeDockerV2 {
2
- Manifest = "application/vnd.docker.distribution.manifest.v2+json"
3
- }
4
- export declare enum MediaTypeOciV1 {
5
- Manifest = "application/vnd.oci.image.manifest.v1+json",
6
- Index = "application/vnd.oci.image.index.v1+json"
7
- }
8
- export declare function head(rawUrl: string, mediaType: string, optsParam?: Record<string, any>): Promise<any>;
9
- export declare function get(rawUrl: string, mediaType: string, optsParam?: Record<string, any>): Promise<any>;
10
- export declare function download(rawUrl: string, localPath: string, optsParam?: Record<string, any>): Promise<void>;
11
- /**
12
- * Returns all containers in a pod
13
- * @param {string} iref image reference
14
- * @param {array} pubkeys list of paths to node crypto code signing pubkeys
15
- * @returns {boolean} whether the iref was signed by a key in the pubkeys
16
- */
17
- export declare function verifyImage(iref: string, pubkeys: string[], tlsCrts?: string[]): Promise<boolean>;
18
- //# sourceMappingURL=cosign.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cosign.d.ts","sourceRoot":"","sources":["../../../src/sdk/cosign.ts"],"names":[],"mappings":"AAWA,oBAAY,iBAAiB;IAC3B,QAAQ,yDAAyD;CAClE;AAED,oBAAY,cAAc;IACxB,QAAQ,+CAA+C;IACvD,KAAK,4CAA4C;CAClD;AAGD,wBAAsB,IAAI,CACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAClC,OAAO,CAAC,GAAG,CAAC,CAwCd;AAGD,wBAAsB,GAAG,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAClC,OAAO,CAAC,GAAG,CAAC,CAmDd;AAGD,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAClC,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAMD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,OAAO,CAAC,CAsIlB"}
@@ -1,14 +0,0 @@
1
- {
2
- "arrowParens": "avoid",
3
- "bracketSameLine": false,
4
- "bracketSpacing": true,
5
- "embeddedLanguageFormatting": "auto",
6
- "insertPragma": false,
7
- "printWidth": 120,
8
- "quoteProps": "as-needed",
9
- "requirePragma": false,
10
- "semi": true,
11
- "useTabs": false,
12
- "vueIndentScriptAndStyle": false,
13
- "tabWidth": 2
14
- }
package/src/sdk/cosign.ts DELETED
@@ -1,327 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
-
4
- import { https } from "follow-redirects";
5
- import { readFile, unlink } from "node:fs/promises";
6
- import { createWriteStream } from "node:fs";
7
- import * as crypto from "node:crypto";
8
- import { PublicKeyDetails, TrustedRoot } from "@sigstore/protobuf-specs";
9
- import { bundleFromJSON } from "@sigstore/bundle";
10
- import { toSignedEntity, toTrustMaterial, Verifier } from "@sigstore/verify";
11
-
12
- export enum MediaTypeDockerV2 {
13
- Manifest = "application/vnd.docker.distribution.manifest.v2+json",
14
- }
15
-
16
- export enum MediaTypeOciV1 {
17
- Manifest = "application/vnd.oci.image.manifest.v1+json",
18
- Index = "application/vnd.oci.image.index.v1+json",
19
- }
20
-
21
- /* eslint-disable @typescript-eslint/no-explicit-any */
22
- export async function head(
23
- rawUrl: string,
24
- mediaType: string,
25
- optsParam: Record<string, any> = {},
26
- ): Promise<any> {
27
- const url = new URL(rawUrl);
28
-
29
- return new Promise((resolve, reject) => {
30
- const opts = {
31
- protocol: url.protocol,
32
- hostname: url.hostname,
33
- port: url.port,
34
- path: url.pathname,
35
- method: "HEAD",
36
- headers: { Accept: mediaType },
37
- ...optsParam,
38
- };
39
-
40
- https
41
- .request(opts, resp => {
42
- const { statusCode } = resp;
43
-
44
- let error;
45
- if (!statusCode?.toString().startsWith("2") && !statusCode?.toString().startsWith("3")) {
46
- reject(new Error(`err: status code: ${statusCode}: expected 2xx|3xx`));
47
- error = true;
48
- }
49
-
50
- if (error) {
51
- resp.resume();
52
- return;
53
- }
54
-
55
- resp.setEncoding("utf8");
56
-
57
- resp.on("data", () => {});
58
-
59
- resp.on("end", () => {
60
- resolve(resp.headers);
61
- });
62
- })
63
- .on("error", e => reject(e))
64
- .end();
65
- });
66
- }
67
-
68
- /* eslint-disable @typescript-eslint/no-explicit-any */
69
- export async function get(
70
- rawUrl: string,
71
- mediaType: string,
72
- optsParam: Record<string, any> = {},
73
- ): Promise<any> {
74
- const url = new URL(rawUrl);
75
-
76
- return new Promise((resolve, reject) => {
77
- const opts = {
78
- protocol: url.protocol,
79
- hostname: url.hostname,
80
- port: url.port,
81
- path: url.pathname,
82
- method: "GET",
83
- headers: {
84
- "User-Agent": "node",
85
- Accept: mediaType,
86
- },
87
- ...optsParam,
88
- };
89
-
90
- https
91
- .request(opts, resp => {
92
- const { statusCode } = resp;
93
-
94
- let error;
95
-
96
- if (!statusCode?.toString().startsWith("2") && !statusCode?.toString().startsWith("3")) {
97
- console.log(resp.headers);
98
- reject(new Error(`err: status code: ${statusCode}: expected 2xx`));
99
- error = true;
100
- }
101
-
102
- if (error) {
103
- resp.resume();
104
- return;
105
- }
106
-
107
- resp.setEncoding("utf8");
108
-
109
- let raw = "";
110
- resp.on("data", chunk => {
111
- raw += chunk;
112
- });
113
- resp.on("end", () => {
114
- try {
115
- resolve({ head: resp.headers, body: raw });
116
- } catch (e) {
117
- reject(e);
118
- }
119
- });
120
- })
121
- .on("error", e => reject(e))
122
- .end();
123
- });
124
- }
125
-
126
- /* eslint-disable @typescript-eslint/no-explicit-any */
127
- export async function download(
128
- rawUrl: string,
129
- localPath: string,
130
- optsParam: Record<string, any> = {},
131
- ): Promise<void> {
132
- const url = new URL(rawUrl);
133
-
134
- return new Promise((resolve, reject) => {
135
- const opts = {
136
- protocol: url.protocol,
137
- hostname: url.hostname,
138
- port: url.port,
139
- path: url.pathname,
140
- method: "GET",
141
- headers: {
142
- "User-Agent": "node",
143
- Accept: "application/octet-stream",
144
- },
145
- ...optsParam,
146
- };
147
-
148
- https
149
- .request(opts, resp => {
150
- const { statusCode } = resp;
151
-
152
- let error;
153
-
154
- if (!statusCode?.toString().startsWith("2") && !statusCode?.toString().startsWith("3")) {
155
- console.log(resp.headers);
156
- reject(new Error(`err: status code: ${statusCode}: expected 2xx`));
157
- error = true;
158
- }
159
-
160
- if (error) {
161
- resp.resume();
162
- return;
163
- }
164
-
165
- const ws = createWriteStream(localPath).on("finish", () => {
166
- ws.close(() => resolve());
167
- });
168
-
169
- resp.pipe(ws);
170
- })
171
- .on("error", async err => {
172
- await unlink(localPath);
173
- reject(err);
174
- })
175
- .end();
176
- });
177
- }
178
-
179
- //
180
- // TODO: should support using certs too
181
- //
182
-
183
- /**
184
- * Returns all containers in a pod
185
- * @param {string} iref image reference
186
- * @param {array} pubkeys list of paths to node crypto code signing pubkeys
187
- * @returns {boolean} whether the iref was signed by a key in the pubkeys
188
- */
189
- export async function verifyImage(
190
- iref: string,
191
- pubkeys: string[],
192
- tlsCrts?: string[],
193
- ): Promise<boolean> {
194
- const X: Record<string, any> = {};
195
-
196
- // <host---> / <image----------------------->
197
- // / <name---------------> : <tag->
198
- // docker.io / library / hello-world : latest
199
- //
200
- // <host> / <image------------------------------------->
201
- // / <name------------------------------> : <tag>
202
- // ttl.sh / 5dad3c9b-7ccc-4115-be27-c9244e7c0e06 : 2000m
203
-
204
- X.iref = {};
205
- X.iref.raw = iref;
206
- X.iref.host = iref.split("/")[0];
207
- X.iref.image = iref.replace(`${X.iref.host}/`, "");
208
- X.iref.tag = X.iref.image.split(":").at(-1);
209
- X.iref.name = X.iref.image.replace(`:${X.iref.tag}`, "");
210
-
211
- X.manifest = {
212
- url: `https://${X.iref.host}/v2/${X.iref.name}/manifests/${X.iref.tag}`,
213
- };
214
-
215
- const supportsMediaType = async (url: string, mediaType: string): Promise<boolean> => {
216
- return (await head(url, mediaType, { ca: tlsCrts }))["content-type"] === mediaType;
217
- };
218
-
219
- const canOciV1Manifest = async (manifestUrl: string): Promise<boolean> => {
220
- return supportsMediaType(manifestUrl, MediaTypeOciV1.Manifest);
221
- };
222
-
223
- const canDockerV2Manifest = async (manifestUrl: string): Promise<boolean> => {
224
- return supportsMediaType(manifestUrl, MediaTypeDockerV2.Manifest);
225
- };
226
-
227
- // prettier-ignore
228
- const manifestResp =
229
- await canOciV1Manifest(X.manifest.url) ? await get(X.manifest.url, MediaTypeOciV1.Manifest, {ca: tlsCrts}) :
230
- await canDockerV2Manifest(X.manifest.url) ? await get(X.manifest.url, MediaTypeDockerV2.Manifest, {ca: tlsCrts}) :
231
- (():never => { throw "Can't pull image manifest with supported MediaType." })();
232
- X.manifest.content = manifestResp.body;
233
-
234
- X.manifest.digest = `sha256:${crypto
235
- .createHash("sha256")
236
- .update(X.manifest.content)
237
- .digest("hex")
238
- .toString()}`;
239
-
240
- X.sig = {};
241
- X.sig.tag = `${X.manifest.digest.replace(":", "-")}.sig`;
242
- X.sig.triangulated = `${X.iref.host}/${X.iref.name}:${X.sig.tag}`;
243
- X.sig.url = `https://${X.iref.host}/v2/${X.iref.name}/manifests/${X.sig.tag}`;
244
-
245
- const sigManifestResp = await get(X.sig.url, MediaTypeOciV1.Manifest, { ca: tlsCrts });
246
- X.sig.manifest = sigManifestResp.body;
247
-
248
- const cosignSigLayer = JSON.parse(X.sig.manifest).layers.filter((f: any) =>
249
- Object.hasOwn(f?.annotations, "dev.cosignproject.cosign/signature"),
250
- )[0];
251
-
252
- X.sig.blob = {};
253
- X.sig.blob.digest = cosignSigLayer.digest;
254
- X.sig.blob.signature = cosignSigLayer.annotations["dev.cosignproject.cosign/signature"];
255
- X.sig.blob.url = `https://${X.iref.host}/v2/${X.iref.name}/blobs/${X.sig.blob.digest}`;
256
-
257
- const sigBlobResp = await get(X.sig.blob.url, "application/octet-stream", { ca: tlsCrts });
258
- X.sig.blob.content = sigBlobResp.body;
259
-
260
- let verified = false;
261
-
262
- for (const pubkey of pubkeys) {
263
- // https://github.com/sigstore/sigstore-js/blob/main/packages/verify/src/__tests__/verifier.test.ts
264
- const pubKeyRaw = await readFile(`${pubkey}`, { encoding: "utf8" });
265
- const pubKey = crypto.createPublicKey({
266
- key: pubKeyRaw,
267
- format: "pem",
268
- encoding: "utf-8",
269
- });
270
-
271
- const trustedRoot = {
272
- tlogs: [],
273
- ctlogs: [],
274
- timestampAuthorities: [],
275
- certificateAuthorities: [],
276
- } as unknown as TrustedRoot;
277
-
278
- const keys = {
279
- hint: {
280
- rawBytes: pubKey.export({ type: "spki", format: "der" }),
281
- keyDetails: PublicKeyDetails.PKIX_ECDSA_P256_SHA_256,
282
- },
283
- };
284
- const trustMaterial = toTrustMaterial(trustedRoot, keys);
285
-
286
- const subject = new Verifier(trustMaterial, {
287
- ctlogThreshold: 0,
288
- tlogThreshold: 0,
289
- tsaThreshold: 0,
290
- });
291
-
292
- const bundle = bundleFromJSON({
293
- mediaType: "application/vnd.dev.sigstore.bundle+json;version=0.1",
294
- verificationMaterial: {
295
- publicKey: {
296
- hint: "hint",
297
- },
298
- tlogEntries: [],
299
- timestampVerificationData: {
300
- rfc3161Timestamps: [],
301
- },
302
- },
303
- messageSignature: {
304
- messageDigest: {
305
- algorithm: "SHA2_256",
306
- digest: crypto.createHash("sha256").update(X.sig.blob.content).digest().toString(),
307
- },
308
- signature: X.sig.blob.signature,
309
- },
310
- });
311
-
312
- const signedEntity = toSignedEntity(bundle, Buffer.from(X.sig.blob.content));
313
-
314
- try {
315
- subject.verify(signedEntity);
316
- verified = true;
317
- break;
318
- } catch (e) {
319
- if (e.message.includes("signature verification failed")) {
320
- continue;
321
- }
322
- throw e;
323
- }
324
- }
325
-
326
- return verified;
327
- }