From 25afe6d38309aa002575d0743f58b352bf2e3f19 Mon Sep 17 00:00:00 2001 From: Gergely Danyi Date: Wed, 25 Oct 2023 11:24:25 -0500 Subject: [PATCH] Apply formatting --- .husky/pre-commit | 4 + cli/index.ts | 5 +- .../__snapshots__/filter.test.ts.snap | 56 +++++++++--- client/__tests__/filter.test.ts | 87 ++++--------------- client/filter.ts | 17 ++-- client/index.ts | 31 ++++--- client/jwks.ts | 3 +- log/index.ts | 1 + package.json | 4 +- server/__tests__/e2e.test.ts | 5 +- server/auth.ts | 3 +- server/index.ts | 25 +++--- server/proxy.ts | 11 ++- server/testing/testExpressApp.ts | 3 +- yarn.lock | 2 +- 15 files changed, 122 insertions(+), 135 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..72c88ce --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn run lint-staged diff --git a/cli/index.ts b/cli/index.ts index f5beeef..a6ec30c 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -1,10 +1,9 @@ -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; - import { JsonRpcClient } from "../client"; import { Backoff } from "../client/backoff"; import { createLogger } from "../log"; import { runApp } from "../server"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; const logger = createLogger({ name: "cli" }); diff --git a/client/__tests__/__snapshots__/filter.test.ts.snap b/client/__tests__/__snapshots__/filter.test.ts.snap index 3e047ed..8af1c05 100644 --- a/client/__tests__/__snapshots__/filter.test.ts.snap +++ b/client/__tests__/__snapshots__/filter.test.ts.snap @@ -1,32 +1,68 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json path filter returns only selected fields 1`] = ` +exports[`json path filter returns complete data on error 1`] = ` { "data": { + "apiVersion": "rbac.authorization.k8s.io/v1", "items": [ { "metadata": { + "managedFields": [ + { + "manager": "clusterrole-aggregation-controller", + "operation": "Apply", + }, + { + "manager": "kube-apiserver", + "operation": "Update", + }, + ], "name": "admin", + "resourceVersion": "345", + "uid": "4251609f-b3ca-4875-8ec6-76b40ab62748", }, }, { "metadata": { + "managedFields": [ + { + "manager": "kubectl-client-side-apply", + "operation": "Update", + }, + ], "name": "aws-node", + "resourceVersion": "273", + "uid": "63a3bed8-a750-440d-bca9-b5f41c8786c2", }, }, ], "kind": "ClusterRoleList", - }, - "headers": { - "audit-id": "6a816d03-d29f-4657-b624-35766c8a5572", - "cache-control": "no-cache, private", - "content-type": "application/json", - "date": "Wed, 25 Oct 2023 06:08:30 GMT", - "transfer-encoding": "chunked", - "x-kubernetes-pf-flowschema-uid": "ef7c3d21-4649-422d-a4fa-e5b85d52f141", - "x-kubernetes-pf-prioritylevel-uid": "a6704cd4-c4fb-48bb-982e-a1ed79bfb12d", + "metadata": { + "resourceVersion": "4423772", + }, }, "status": 200, "statusText": "OK", } `; + +exports[`json path filter returns only selected fields 1`] = ` +{ + "data": { + "items": [ + { + "metadata": { + "name": "admin", + }, + }, + { + "metadata": { + "name": "aws-node", + }, + }, + ], + "kind": "ClusterRoleList", + }, + "status": 200, +} +`; diff --git a/client/__tests__/filter.test.ts b/client/__tests__/filter.test.ts index 4ceea1e..53ad964 100644 --- a/client/__tests__/filter.test.ts +++ b/client/__tests__/filter.test.ts @@ -1,15 +1,6 @@ import { jpFilter as jqFilter } from "client/filter"; const exampleResponse = { - headers: { - "audit-id": "6a816d03-d29f-4657-b624-35766c8a5572", - "cache-control": "no-cache, private", - "content-type": "application/json", - "x-kubernetes-pf-flowschema-uid": "ef7c3d21-4649-422d-a4fa-e5b85d52f141", - "x-kubernetes-pf-prioritylevel-uid": "a6704cd4-c4fb-48bb-982e-a1ed79bfb12d", - date: "Wed, 25 Oct 2023 06:08:30 GMT", - "transfer-encoding": "chunked", - }, status: 200, statusText: "OK", data: { @@ -24,46 +15,14 @@ const exampleResponse = { name: "admin", uid: "4251609f-b3ca-4875-8ec6-76b40ab62748", resourceVersion: "345", - creationTimestamp: "2023-10-02T22:51:23Z", - labels: { - "kubernetes.io/bootstrapping": "rbac-defaults", - }, - annotations: { - "rbac.authorization.kubernetes.io/autoupdate": "true", - }, managedFields: [ { manager: "clusterrole-aggregation-controller", operation: "Apply", - apiVersion: "rbac.authorization.k8s.io/v1", - time: "2023-10-02T22:51:38Z", - fieldsType: "FieldsV1", - fieldsV1: { - "f:rules": {}, - }, }, { manager: "kube-apiserver", operation: "Update", - apiVersion: "rbac.authorization.k8s.io/v1", - time: "2023-10-02T22:51:23Z", - fieldsType: "FieldsV1", - fieldsV1: { - "f:aggregationRule": { - ".": {}, - "f:clusterRoleSelectors": {}, - }, - "f:metadata": { - "f:annotations": { - ".": {}, - "f:rbac.authorization.kubernetes.io/autoupdate": {}, - }, - "f:labels": { - ".": {}, - "f:kubernetes.io/bootstrapping": {}, - }, - }, - }, }, ], }, @@ -73,40 +32,10 @@ const exampleResponse = { name: "aws-node", uid: "63a3bed8-a750-440d-bca9-b5f41c8786c2", resourceVersion: "273", - creationTimestamp: "2023-10-02T22:51:35Z", - labels: { - "app.kubernetes.io/instance": "aws-vpc-cni", - "app.kubernetes.io/name": "aws-node", - "app.kubernetes.io/version": "v1.12.6", - "k8s-app": "aws-node", - }, - annotations: { - "kubectl.kubernetes.io/last-applied-configuration": - '{"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRole","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"aws-vpc-cni","app.kubernetes.io/name":"aws-node","app.kubernetes.io/version":"v1.12.6","k8s-app":"aws-node"},"name":"aws-node"},"rules":[{"apiGroups":["crd.k8s.amazonaws.com"],"resources":["eniconfigs"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["namespaces"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["pods"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["nodes"],"verbs":["list","watch","get","update"]},{"apiGroups":["extensions"],"resources":["*"],"verbs":["list","watch"]},{"apiGroups":["","events.k8s.io"],"resources":["events"],"verbs":["create","patch","list"]}]}\n', - }, managedFields: [ { manager: "kubectl-client-side-apply", operation: "Update", - apiVersion: "rbac.authorization.k8s.io/v1", - time: "2023-10-02T22:51:35Z", - fieldsType: "FieldsV1", - fieldsV1: { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {}, - }, - "f:labels": { - ".": {}, - "f:app.kubernetes.io/instance": {}, - "f:app.kubernetes.io/name": {}, - "f:app.kubernetes.io/version": {}, - "f:k8s-app": {}, - }, - }, - "f:rules": {}, - }, }, ], }, @@ -119,9 +48,23 @@ describe("json path filter", () => { it("returns only selected fields", async () => { const result = await jqFilter( exampleResponse, - "{ headers: .headers, status: .status, statusText: .statusText, data: { kind: .data.kind, items: [.data.items[] | { metadata: { name: .metadata.name } }] } }" + "{ status: .status, data: { kind: .data.kind, items: [.data.items[] | { metadata: { name: .metadata.name } }] } }" ); expect(result).toMatchSnapshot(); }); + + describe("returns original input data", () => { + it("on error", async () => { + const result = await jqFilter(exampleResponse, "{ { }"); + + expect(result).toMatchSnapshot(); + }); + + it("when no filter supplied", async () => { + const result = await jqFilter(exampleResponse, undefined); + + expect(result).toMatchSnapshot(); + }); + }); }); diff --git a/client/filter.ts b/client/filter.ts index 263dcec..8d19e6c 100644 --- a/client/filter.ts +++ b/client/filter.ts @@ -1,5 +1,8 @@ +import { createLogger } from "../log"; import { isArray } from "lodash"; +const logger = createLogger({ name: "filter" }); + // jq-node doesn't support `import` syntax const jq = require("node-jq"); @@ -7,14 +10,18 @@ export const jpFilter = async ( data: any, jqHeader: string | string[] | undefined ): Promise => { - const jpSelectQuery = isArray(jqHeader) ? jqHeader[0] : jqHeader; - if (!jpSelectQuery) { + const query = isArray(jqHeader) ? jqHeader[0] : jqHeader; + if (!query) { return data; } try { - return await jq.run(jpSelectQuery, data, { input: "json", output: "json" }); - } catch (e: any) { - console.log(e); + // jq.run() returns a Promise + return await jq.run(query, data, { input: "json", output: "json" }); + } catch (error: any) { + logger.error( + { error, jpSelectQuery: query }, + "Error running jq query, ignoring filters" + ); return data; } }; diff --git a/client/index.ts b/client/index.ts index 6ddaaad..38f9f31 100644 --- a/client/index.ts +++ b/client/index.ts @@ -1,3 +1,10 @@ +import { DEFAULT_FORWARDED_REQUEST_TIMEOUT_MILLIS } from "../common/constants"; +import { createLogger } from "../log"; +import { ForwardedRequest, ForwardedResponse, JQ_HEADER } from "../types"; +import { deferral } from "../util/deferral"; +import { Backoff } from "./backoff"; +import { jpFilter as jqFilter } from "./filter"; +import { jwt } from "./jwks"; import axios from "axios"; import { JSONRPCClient, @@ -8,14 +15,6 @@ import { isArray, omit } from "lodash"; import { Logger } from "pino"; import WebSocket from "ws"; -import { DEFAULT_FORWARDED_REQUEST_TIMEOUT_MILLIS } from "../common/constants"; -import { createLogger } from "../log"; -import { ForwardedRequest, ForwardedResponse, JQ_HEADER } from "../types"; -import { deferral } from "../util/deferral"; -import { Backoff } from "./backoff"; -import { jpFilter as jqFilter } from "./filter"; -import { jwt } from "./jwks"; - /** * Bi-directional JSON RPC client */ @@ -63,11 +62,7 @@ export class JsonRpcClient { }); axios.interceptors.response.use((response) => { - // Do not log request object, it's lengthy and difficult to filter out the authorization header - this.#logger.debug( - { response: omit(response, "request") }, - "Axios response" - ); + this.#logger.debug({ response }, "Axios response"); return response; }); } @@ -135,7 +130,15 @@ export class JsonRpcClient { this.#webSocket = clientSocket; client.addMethod("call", async (request: ForwardedRequest) => { - this.#logger.debug({ request }, "forwarded request"); + this.#logger.debug( + { + request: { + ...request, + headers: omit(request.headers, "authorization"), + }, + }, + "forwarded request" + ); // The headers are modified: // 1. The Content-Length header may not be accurate for the forwarded request. By removing it, axios can recalculate the correct length. // 2. The Host header should be switched out to the host this client is targeting. diff --git a/client/jwks.ts b/client/jwks.ts index 8fad5e9..66dd1cf 100644 --- a/client/jwks.ts +++ b/client/jwks.ts @@ -1,9 +1,8 @@ +import { privateKeyFile, publicKeyFile } from "../util/jwk-file"; import * as jose from "jose"; import * as fs from "node:fs/promises"; import pinoLogger from "pino"; -import { privateKeyFile, publicKeyFile } from "../util/jwk-file"; - const ALG = "ES384"; // Elliptic curve with 384-bit SHA const logger = pinoLogger({ name: "jwks" }); diff --git a/log/index.ts b/log/index.ts index a9873f7..9f112f1 100644 --- a/log/index.ts +++ b/log/index.ts @@ -22,6 +22,7 @@ export const createLogger = ( // Redact the authorization header that may contain a secret token redact: [ "response.config.headers.authorization", // Axios intercepted response object + "response.request.headers.authorization", // Axios intercepted response object contains the request as well "request.headers.authorization", // Axios intercepted request object + forwarded request object (JSON RPC request) ], formatters: { diff --git a/package.json b/package.json index ffd02de..6dc6b38 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "eslint-plugin-import": "^2.25.2", "eslint-plugin-n": "^15.0.0", "eslint-plugin-promise": "^6.0.0", - "husky": "^8.0.2", + "husky": "^8.0.3", "jest": "29.2.1", "jest-extended": "3.2.4", "jest-mock": "29.2.1", @@ -63,7 +63,7 @@ "start:prod:server": "NODE_PATH=dist node dist/cli server", "start:dev:client": "NODE_ENV=development nodemon cli client", "start:prod:client": "NODE_PATH=dist node dist/cli client", - "prepare": "cd .. && husky install ts/.husky", + "prepare": "husky install", "format": "prettier --write '**/*.{html,js,ts,json,less,md}'", "format:quick": "pretty-quick --branch origin/main" }, diff --git a/server/__tests__/e2e.test.ts b/server/__tests__/e2e.test.ts index 3f394af..3d6bc69 100644 --- a/server/__tests__/e2e.test.ts +++ b/server/__tests__/e2e.test.ts @@ -1,11 +1,10 @@ -import { Server } from "http"; -import request from "supertest"; - import { App, InitContext, runApp } from "../"; // TODO replace supertest with axios requests import { JsonRpcClient } from "../../client"; import { Backoff } from "../../client/backoff"; import { testHttpServer } from "../testing/testExpressApp"; +import { Server } from "http"; +import request from "supertest"; const SERVER_RPC_PORT = 8080; const SERVER_PROXY_PORT = 8081; diff --git a/server/auth.ts b/server/auth.ts index 0e96b2d..63b38a3 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -1,8 +1,7 @@ +import { PublicKeyGetter } from "../types"; import * as jose from "jose"; import pinoLogger from "pino"; -import { PublicKeyGetter } from "../types"; - const AUTH_PATTERN = /Bearer (.*)/; const ALG = "ES384"; diff --git a/server/index.ts b/server/index.ts index 46dea54..15ffabc 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,16 +1,3 @@ -import express, { Express } from "express"; -import { IncomingMessage, Server, ServerResponse } from "http"; -import { - JSONRPCClient, - JSONRPCServer, - JSONRPCServerAndClient, -} from "json-rpc-2.0"; -import { randomUUID } from "node:crypto"; -import { Duplex } from "node:stream"; -import { Logger } from "pino"; -import audit from "pino-http"; -import { ServerOptions, WebSocket, WebSocketServer } from "ws"; - import { RetryOptions, retryWithBackoff } from "../client/backoff"; import { DEFAULT_WEBSOCKET_CALL_TIMEOUT_MILLIS } from "../common/constants"; import { createLogger } from "../log"; @@ -25,6 +12,18 @@ import { ChannelNotFoundError } from "./error"; import { ensureKey } from "./key-cache"; import { httpProxyApp } from "./proxy"; import { httpError } from "./util"; +import express, { Express } from "express"; +import { IncomingMessage, Server, ServerResponse } from "http"; +import { + JSONRPCClient, + JSONRPCServer, + JSONRPCServerAndClient, +} from "json-rpc-2.0"; +import { randomUUID } from "node:crypto"; +import { Duplex } from "node:stream"; +import { Logger } from "pino"; +import audit from "pino-http"; +import { ServerOptions, WebSocket, WebSocketServer } from "ws"; const logger = createLogger({ name: "server" }); diff --git a/server/proxy.ts b/server/proxy.ts index 65d642f..2019606 100644 --- a/server/proxy.ts +++ b/server/proxy.ts @@ -1,9 +1,3 @@ -import express from "express"; -import { JsonStreamStringify } from "json-stream-stringify"; -import { omit } from "lodash"; -import { pathToRegexp } from "path-to-regexp"; -import audit from "pino-http"; - import { RetryOptions } from "../client/backoff"; import { createLogger } from "../log"; import { RemoteClientRpcServer } from "../server"; @@ -13,6 +7,11 @@ import { ForwardedRequestOptions, IncomingRequest, } from "../types"; +import express from "express"; +import { JsonStreamStringify } from "json-stream-stringify"; +import { omit } from "lodash"; +import { pathToRegexp } from "path-to-regexp"; +import audit from "pino-http"; const logger = createLogger({ name: "proxy" }); diff --git a/server/testing/testExpressApp.ts b/server/testing/testExpressApp.ts index 38e3f7c..5958f5e 100644 --- a/server/testing/testExpressApp.ts +++ b/server/testing/testExpressApp.ts @@ -1,8 +1,7 @@ +import { createLogger } from "../../log"; import express, { Router } from "express"; import audit from "pino-http"; -import { createLogger } from "../../log"; - const logger = createLogger({ name: "testHttpServer" }); export const testHttpServer = (port: number) => { diff --git a/yarn.lock b/yarn.lock index 5b935fa..6bbba67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3256,7 +3256,7 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -husky@^8.0.2: +husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==