From fac129ecc676c255838d3637a2c3c86968674823 Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 10 Oct 2023 14:33:31 +0200 Subject: [PATCH 01/14] chore: add cert init script --- admission-webhook-init/.gitignore | 65 ++ admission-webhook-init/package-lock.json | 914 +++++++++++++++++++++++ admission-webhook-init/package.json | 23 + admission-webhook-init/src/index.ts | 105 +++ admission-webhook-init/tsconfig.json | 14 + 5 files changed, 1121 insertions(+) create mode 100644 admission-webhook-init/.gitignore create mode 100644 admission-webhook-init/package-lock.json create mode 100644 admission-webhook-init/package.json create mode 100644 admission-webhook-init/src/index.ts create mode 100644 admission-webhook-init/tsconfig.json diff --git a/admission-webhook-init/.gitignore b/admission-webhook-init/.gitignore new file mode 100644 index 0000000..f4cefe8 --- /dev/null +++ b/admission-webhook-init/.gitignore @@ -0,0 +1,65 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# 0x +profile-* + +# mac files +.DS_Store + +# vim swap files +*.swp + +# webstorm +.idea + +# vscode +.vscode +*code-workspace + +# clinic +profile* +*clinic* +*flamegraph* + +# generated code +examples/typescript-server.js +test/types/index.js + +# compiled app +dist diff --git a/admission-webhook-init/package-lock.json b/admission-webhook-init/package-lock.json new file mode 100644 index 0000000..632754d --- /dev/null +++ b/admission-webhook-init/package-lock.json @@ -0,0 +1,914 @@ +{ + "name": "admission-webhook-init", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "admission-webhook-init", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@kubernetes/client-node": "^0.18.1", + "node-forge": "^1.3.1" + }, + "devDependencies": { + "@types/node": "^20.1.7", + "@types/node-forge": "^1.3.6", + "typescript": "^4.6.3" + } + }, + "node_modules/@kubernetes/client-node": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.18.1.tgz", + "integrity": "sha512-F3JiK9iZnbh81O/da1tD0h8fQMi/MDttWc/JydyUVnjPEom55wVfnpl4zQ/sWD4uKB8FlxYRPiLwV2ZXB+xPKw==", + "dependencies": { + "@types/js-yaml": "^4.0.1", + "@types/node": "^18.11.17", + "@types/request": "^2.47.1", + "@types/ws": "^8.5.3", + "byline": "^5.0.0", + "isomorphic-ws": "^5.0.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^7.2.0", + "request": "^2.88.0", + "rfc4648": "^1.3.0", + "stream-buffers": "^3.0.2", + "tar": "^6.1.11", + "tmp-promise": "^3.0.2", + "tslib": "^2.4.1", + "underscore": "^1.13.6", + "ws": "^8.11.0" + }, + "optionalDependencies": { + "openid-client": "^5.3.0" + } + }, + "node_modules/@kubernetes/client-node/node_modules/@types/node": { + "version": "18.18.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.4.tgz", + "integrity": "sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==" + }, + "node_modules/@types/caseless": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.3.tgz", + "integrity": "sha512-ZD/NsIJYq/2RH+hY7lXmstfp/v9djGt9ah+xRQ3pcgR79qiKsG4pLl25AI7IcXxVO8dH9GiBE5rAknC0ePntlw==" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", + "integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==" + }, + "node_modules/@types/node": { + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.3.tgz", + "integrity": "sha512-jxiZQFpb+NlH5kjW49vXxvxTjeeqlbsnTAdBTKpzEdPs9itay7MscYXz3Fo9VYFEsfQ6LJFitHad3faerLAjCw==" + }, + "node_modules/@types/node-forge": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.6.tgz", + "integrity": "sha512-rOLL54+BcPO1BV0ogdKCtfmohdL3WW3ODCbnh8gA8ZWPYfHM0aVFyXQljme5DoICH29/2JZOXqgkS9CEd/NmSQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/request": { + "version": "2.48.9", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.9.tgz", + "integrity": "sha512-4mi2hYsvPAhe8RXjk5DKB09sAUzbK68T2XjORehHdWyxFoX2zUnfi1VQ5wU4Md28H/5+uB4DkxY9BS4B87N/0A==", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==" + }, + "node_modules/@types/ws": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/jose": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz", + "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonpath-plus": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", + "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "optional": true, + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openid-client": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz", + "integrity": "sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==", + "optional": true, + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/rfc4648": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.2.tgz", + "integrity": "sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/admission-webhook-init/package.json b/admission-webhook-init/package.json new file mode 100644 index 0000000..2386f87 --- /dev/null +++ b/admission-webhook-init/package.json @@ -0,0 +1,23 @@ +{ + "name": "admission-webhook-init", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node ./dist/index.js", + "prod": "npm run build && npm run start" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@kubernetes/client-node": "^0.18.1", + "node-forge": "^1.3.1" + }, + "devDependencies": { + "@types/node": "^20.1.7", + "@types/node-forge": "^1.3.6", + "typescript": "^4.6.3" + } +} diff --git a/admission-webhook-init/src/index.ts b/admission-webhook-init/src/index.ts new file mode 100644 index 0000000..49c2262 --- /dev/null +++ b/admission-webhook-init/src/index.ts @@ -0,0 +1,105 @@ +import fs from 'fs'; +import forge from 'node-forge'; + +const keyDir = process.argv[2]; +if (!keyDir) { + console.error('Missing key directory argument'); + process.exit(1); +} + +fs.mkdirSync(keyDir, { mode: 0o700, recursive: true }); +process.chdir(keyDir); + +const caKeys = forge.pki.rsa.generateKeyPair(2048); + +const caCert = forge.pki.createCertificate(); +caCert.publicKey = caKeys.publicKey; +caCert.serialNumber = '01'; +caCert.validity.notBefore = new Date(); +caCert.validity.notAfter = new Date(); +caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); + +const attrs = [ + { name: 'commonName', value: 'Monokle Admission Controller CA' }, +]; +caCert.setSubject(attrs); +caCert.setIssuer(attrs); +caCert.setExtensions([ + { + name: 'basicConstraints', + cA: true, + }, +]); + +caCert.sign(caKeys.privateKey); + +fs.writeFileSync('ca.key', forge.pki.privateKeyToPem(caKeys.privateKey)); +fs.writeFileSync('ca.crt', forge.pki.certificateToPem(caCert)); + +const serverKeys = forge.pki.rsa.generateKeyPair(2048); +const csr = forge.pki.createCertificationRequest(); +csr.publicKey = serverKeys.publicKey; +csr.setSubject([ + { + name: 'commonName', + value: 'webhook-server.monokle-admission-controller.svc', + }, +]); +// This is according to docs here: https://www.npmjs.com/package/node-forge#pkcs10. +// Looks like some types are incorrect. +(csr as any).setAttributes([ + { + name: 'extensionRequest', + extensions: [ + { + name: 'subjectAltName', + altNames: [ + { + type: 2, + value: 'webhook-server.monokle-admission-controller.svc', + }, + ], + }, + ], + }, +]); +csr.sign(serverKeys.privateKey); + +const serverCert = forge.pki.createCertificate(); +serverCert.publicKey = csr.publicKey; +serverCert.serialNumber = '01'; +serverCert.validity.notBefore = new Date(); +serverCert.validity.notAfter = new Date(); +serverCert.validity.notAfter.setFullYear(serverCert.validity.notBefore.getFullYear() + 1); +serverCert.setSubject(csr.subject.attributes); +serverCert.setIssuer(caCert.subject.attributes); +serverCert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + }, + { + name: 'keyUsage', + nonRepudiation: true, + digitalSignature: true, + keyEncipherment: true, + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + }, + { + name: 'subjectAltName', + altNames: [ + { + type: 2, + value: 'webhook-server.monokle-admission-controller.svc', + }, + ], + }, +]); +serverCert.sign(caKeys.privateKey, forge.md.sha256.create()); + +fs.writeFileSync('webhook-server-tls.key', forge.pki.privateKeyToPem(serverKeys.privateKey)); +fs.writeFileSync('webhook-server-tls.crt', forge.pki.certificateToPem(serverCert)); diff --git a/admission-webhook-init/tsconfig.json b/admission-webhook-init/tsconfig.json new file mode 100644 index 0000000..bf8c6f0 --- /dev/null +++ b/admission-webhook-init/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "node", + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "strict": true /* Enable all strict type-checking options. */, + }, + "exclude": ["node_modules", "dist"], + "include": ["src"] +} \ No newline at end of file From 24aecd8451e3a7f63b3b8a103f715f340031b9b3 Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 10 Oct 2023 14:34:17 +0200 Subject: [PATCH 02/14] refactor: use new cert init script in deploy script --- scripts/deploy.sh | 4 +-- scripts/generate-keys.sh | 55 ---------------------------------------- 2 files changed, 2 insertions(+), 57 deletions(-) delete mode 100755 scripts/generate-keys.sh diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3dd2316..8f9a1bf 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -26,8 +26,8 @@ templdir="${basedir}/../k8s/templates" resdir="${basedir}/../k8s/manifests" # Generate keys into a temporary directory. -echo "Generating TLS keys ..." -"${basedir}/generate-keys.sh" "$keydir" +echo "Generating TLS keys with node ..." +node "${basedir}/../admission-webhook-init/dist/index.js" "$keydir" # Create the `monokle-admission-controller` namespace. This cannot be part of the YAML file as we first need to create the TLS secret, # which would fail otherwise. diff --git a/scripts/generate-keys.sh b/scripts/generate-keys.sh deleted file mode 100755 index 7ecb23d..0000000 --- a/scripts/generate-keys.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 StackRox Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# generate-keys.sh -# -# Generate a (self-signed) CA certificate and a certificate and private key to be used by the webhook demo server. -# The certificate will be issued for the Common Name (CN) of `webhook-server.monokle-admission-controller.svc`, which is the -# cluster-internal DNS name for the service. -# -# NOTE: THIS SCRIPT EXISTS FOR DEMO PURPOSES ONLY. DO NOT USE IT FOR YOUR PRODUCTION WORKLOADS. - -: ${1?'missing key directory'} - -key_dir="$1" - -chmod 0700 "$key_dir" -cd "$key_dir" - -cat >server.conf < Date: Tue, 10 Oct 2023 14:46:12 +0200 Subject: [PATCH 03/14] chore: restructure code --- Dockerfile | 6 +++--- .../init}/.gitignore | 0 .../init}/package-lock.json | 0 .../init}/package.json | 0 .../init}/src/index.ts | 0 .../init}/tsconfig.json | 0 .../server}/.gitignore | 0 .../server}/package-lock.json | 0 .../server}/package.json | 0 .../server}/src/index.ts | 0 .../server}/src/utils/get-informer.ts | 0 .../server}/src/utils/policy-manager.ts | 0 .../server}/src/utils/policy-postprocessor.ts | 0 .../server}/src/utils/validation-server.ts | 0 .../server}/src/utils/validator-manager.ts | 0 .../server}/tsconfig.json | 0 scripts/deploy.sh | 2 +- 17 files changed, 4 insertions(+), 4 deletions(-) rename {admission-webhook-init => admission-controller/init}/.gitignore (100%) rename {admission-webhook-init => admission-controller/init}/package-lock.json (100%) rename {admission-webhook-init => admission-controller/init}/package.json (100%) rename {admission-webhook-init => admission-controller/init}/src/index.ts (100%) rename {admission-webhook-init => admission-controller/init}/tsconfig.json (100%) rename {admission-webhook => admission-controller/server}/.gitignore (100%) rename {admission-webhook => admission-controller/server}/package-lock.json (100%) rename {admission-webhook => admission-controller/server}/package.json (100%) rename {admission-webhook => admission-controller/server}/src/index.ts (100%) rename {admission-webhook => admission-controller/server}/src/utils/get-informer.ts (100%) rename {admission-webhook => admission-controller/server}/src/utils/policy-manager.ts (100%) rename {admission-webhook => admission-controller/server}/src/utils/policy-postprocessor.ts (100%) rename {admission-webhook => admission-controller/server}/src/utils/validation-server.ts (100%) rename {admission-webhook => admission-controller/server}/src/utils/validator-manager.ts (100%) rename {admission-webhook => admission-controller/server}/tsconfig.json (100%) diff --git a/Dockerfile b/Dockerfile index dd231e0..0eb757e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ FROM node:18.16.0-bullseye-slim WORKDIR /workdir -COPY admission-webhook/package*.json ./ +COPY admission-controller/server/package*.json ./ RUN npm ci --ignore-scripts -COPY ./admission-webhook/src ./src -COPY ./admission-webhook/tsconfig.json ./ +COPY ./admission-controller/server/src ./src +COPY ./admission-controller/server/tsconfig.json ./ CMD ["npm", "run", "prod"] EXPOSE 8443 diff --git a/admission-webhook-init/.gitignore b/admission-controller/init/.gitignore similarity index 100% rename from admission-webhook-init/.gitignore rename to admission-controller/init/.gitignore diff --git a/admission-webhook-init/package-lock.json b/admission-controller/init/package-lock.json similarity index 100% rename from admission-webhook-init/package-lock.json rename to admission-controller/init/package-lock.json diff --git a/admission-webhook-init/package.json b/admission-controller/init/package.json similarity index 100% rename from admission-webhook-init/package.json rename to admission-controller/init/package.json diff --git a/admission-webhook-init/src/index.ts b/admission-controller/init/src/index.ts similarity index 100% rename from admission-webhook-init/src/index.ts rename to admission-controller/init/src/index.ts diff --git a/admission-webhook-init/tsconfig.json b/admission-controller/init/tsconfig.json similarity index 100% rename from admission-webhook-init/tsconfig.json rename to admission-controller/init/tsconfig.json diff --git a/admission-webhook/.gitignore b/admission-controller/server/.gitignore similarity index 100% rename from admission-webhook/.gitignore rename to admission-controller/server/.gitignore diff --git a/admission-webhook/package-lock.json b/admission-controller/server/package-lock.json similarity index 100% rename from admission-webhook/package-lock.json rename to admission-controller/server/package-lock.json diff --git a/admission-webhook/package.json b/admission-controller/server/package.json similarity index 100% rename from admission-webhook/package.json rename to admission-controller/server/package.json diff --git a/admission-webhook/src/index.ts b/admission-controller/server/src/index.ts similarity index 100% rename from admission-webhook/src/index.ts rename to admission-controller/server/src/index.ts diff --git a/admission-webhook/src/utils/get-informer.ts b/admission-controller/server/src/utils/get-informer.ts similarity index 100% rename from admission-webhook/src/utils/get-informer.ts rename to admission-controller/server/src/utils/get-informer.ts diff --git a/admission-webhook/src/utils/policy-manager.ts b/admission-controller/server/src/utils/policy-manager.ts similarity index 100% rename from admission-webhook/src/utils/policy-manager.ts rename to admission-controller/server/src/utils/policy-manager.ts diff --git a/admission-webhook/src/utils/policy-postprocessor.ts b/admission-controller/server/src/utils/policy-postprocessor.ts similarity index 100% rename from admission-webhook/src/utils/policy-postprocessor.ts rename to admission-controller/server/src/utils/policy-postprocessor.ts diff --git a/admission-webhook/src/utils/validation-server.ts b/admission-controller/server/src/utils/validation-server.ts similarity index 100% rename from admission-webhook/src/utils/validation-server.ts rename to admission-controller/server/src/utils/validation-server.ts diff --git a/admission-webhook/src/utils/validator-manager.ts b/admission-controller/server/src/utils/validator-manager.ts similarity index 100% rename from admission-webhook/src/utils/validator-manager.ts rename to admission-controller/server/src/utils/validator-manager.ts diff --git a/admission-webhook/tsconfig.json b/admission-controller/server/tsconfig.json similarity index 100% rename from admission-webhook/tsconfig.json rename to admission-controller/server/tsconfig.json diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 8f9a1bf..02e51cc 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -27,7 +27,7 @@ resdir="${basedir}/../k8s/manifests" # Generate keys into a temporary directory. echo "Generating TLS keys with node ..." -node "${basedir}/../admission-webhook-init/dist/index.js" "$keydir" +node "${basedir}/../admission-controller/init/dist/index.js" "$keydir" # Create the `monokle-admission-controller` namespace. This cannot be part of the YAML file as we first need to create the TLS secret, # which would fail otherwise. From 18844f2c2fa3d4347ddd8759147a4d3059ae1518 Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 10 Oct 2023 16:40:24 +0200 Subject: [PATCH 04/14] refactor: ignore system and own namespace from webhook config level --- k8s/templates/deployment.yaml.template | 2 +- k8s/templates/webhook.yaml.template | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/k8s/templates/deployment.yaml.template b/k8s/templates/deployment.yaml.template index 253e8fb..51ee4d3 100644 --- a/k8s/templates/deployment.yaml.template +++ b/k8s/templates/deployment.yaml.template @@ -33,7 +33,7 @@ spec: - name: MONOKLE_LOG_LEVEL value: DEBUG - name: MONOKLE_IGNORE_NAMESPACES - value: 'kube-node-lease,kube-public,kube-system,monokle-admission-controller' + value: '' volumes: - name: webhook-tls-certs secret: diff --git a/k8s/templates/webhook.yaml.template b/k8s/templates/webhook.yaml.template index 61157d6..32d61ac 100644 --- a/k8s/templates/webhook.yaml.template +++ b/k8s/templates/webhook.yaml.template @@ -12,6 +12,15 @@ webhooks: namespace: monokle-admission-controller path: "/validate" caBundle: ${CA_PEM_B64} + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-node-lease + - kube-public + - kube-system + - monokle-admission-controller rules: - operations: ["CREATE", "UPDATE"] apiGroups: ["*"] From 0dcf006b28d65021729f183808346a85cfcff6b6 Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 10 Oct 2023 16:41:11 +0200 Subject: [PATCH 05/14] chore: add tmp test snippets --- admission-controller/init/snippets.js | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 admission-controller/init/snippets.js diff --git a/admission-controller/init/snippets.js b/admission-controller/init/snippets.js new file mode 100644 index 0000000..7a7b65c --- /dev/null +++ b/admission-controller/init/snippets.js @@ -0,0 +1,84 @@ +import k8s from '@kubernetes/client-node'; +import forge from 'node-forge'; + +// ----- +// Read cert from secret +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); + +const k8sApi = kc.makeApiClient(k8s.CoreV1Api); + +k8sApi.listNamespacedSecret('monokle-admission-controller').then((res) => { + const body = res.body;// as k8s.V1SecretList; + + body.items.forEach((item) => { + // console.log(item.metadata); + // console.log(item.data); + + if (item.data?.['tls.crt']) { + console.log(item.data['tls.crt']); + console.log(Buffer.from(item.data['tls.crt'], 'base64').toString('utf8')) + + const crt = forge.pki.certificateFromPem(Buffer.from(item.data['tls.crt'], 'base64').toString('utf8')); + console.log(crt); + + console.log(crt.validity); + } + }); +}); + +// ----- +// Write cert to secret +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); + +const k8sApi = kc.makeApiClient(k8s.CoreV1Api); + +k8sApi.createNamespacedSecret('monokle-admission-controller', { + apiVersion: 'v1', + kind: 'Secret', + type: 'kubernetes.io/tls', + metadata: { + name: 'monokle-webhook-server-cert', + }, + data: { + 'tls.crt': Buffer.from('test').toString('base64'), + 'tls.key': Buffer.from('test').toString('base64'), + }, +}).then((res) => { + console.log(res.body); +}); + +// ----- +// Update 'ValidatingWebhookConfiguration' with new cert +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); + +const k8sApi = kc.makeApiClient(k8s.AdmissionregistrationV1Api); +const client = k8s.KubernetesObjectApi.makeApiClient(kc); + +k8sApi.listValidatingWebhookConfiguration().then((res) => { + // console.log(res.body); + // console.log(res.body.items[0].webhooks); + + const wh = res.body.items[0].webhooks; + wh[0].clientConfig.caBundle = 'test'; + + // https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ + k8sApi.patchValidatingWebhookConfiguration('demo-webhook', { + webhooks: wh, + metadata: { + labels: { + 'monokle.io/updated': Date.now().toString(), + }, + } + }, + undefined, undefined, 'Monokle', undefined, undefined, { + headers: { + 'Content-Type': 'application/merge-patch+json', + }, + }).then((res) => { + console.log(res.body); + console.log(res.body.webhooks); + }); +}); From 8cb5b21530f214d4e7775ae1e40b6102a9db9a73 Mon Sep 17 00:00:00 2001 From: f1ames Date: Wed, 11 Oct 2023 12:09:03 +0200 Subject: [PATCH 06/14] feat: implement cert logic for init container [WIP] --- admission-controller/init/package-lock.json | 231 +++++++++++++++++- admission-controller/init/package.json | 3 +- admission-controller/init/snippets.js | 84 ------- admission-controller/init/src/index.ts | 125 +++------- .../init/src/utils/certificates.ts | 105 ++++++++ .../init/src/utils/kubernetes.ts | 128 ++++++++++ admission-controller/init/src/utils/logger.ts | 26 ++ 7 files changed, 521 insertions(+), 181 deletions(-) delete mode 100644 admission-controller/init/snippets.js create mode 100644 admission-controller/init/src/utils/certificates.ts create mode 100644 admission-controller/init/src/utils/kubernetes.ts create mode 100644 admission-controller/init/src/utils/logger.ts diff --git a/admission-controller/init/package-lock.json b/admission-controller/init/package-lock.json index 632754d..1510815 100644 --- a/admission-controller/init/package-lock.json +++ b/admission-controller/init/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "@kubernetes/client-node": "^0.18.1", - "node-forge": "^1.3.1" + "node-forge": "^1.3.1", + "pino": "^8.16.0" }, "devDependencies": { "@types/node": "^20.1.7", @@ -97,6 +98,17 @@ "@types/node": "*" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -138,6 +150,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -156,6 +176,25 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -173,6 +212,29 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -243,6 +305,22 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -266,6 +344,14 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -376,6 +462,25 @@ "npm": ">=1.3.7" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -588,6 +693,14 @@ "node": "^10.13.0 || >=12.0.0" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -624,6 +737,54 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "node_modules/pino": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.0.tgz", + "integrity": "sha512-UUmvQ/7KTZt/vHjhRrnyS7h+J7qPBQnpG80V56xmIC+o9IqYmQOw/UIny9S9zYDfRBR0ClouCr464EkBMIT7Fw==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -645,6 +806,34 @@ "node": ">=0.6" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -727,11 +916,35 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sonic-boom": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", + "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -764,6 +977,14 @@ "node": ">= 0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/tar": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", @@ -780,6 +1001,14 @@ "node": ">=10" } }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", diff --git a/admission-controller/init/package.json b/admission-controller/init/package.json index 2386f87..3eea333 100644 --- a/admission-controller/init/package.json +++ b/admission-controller/init/package.json @@ -13,7 +13,8 @@ "license": "ISC", "dependencies": { "@kubernetes/client-node": "^0.18.1", - "node-forge": "^1.3.1" + "node-forge": "^1.3.1", + "pino": "^8.16.0" }, "devDependencies": { "@types/node": "^20.1.7", diff --git a/admission-controller/init/snippets.js b/admission-controller/init/snippets.js deleted file mode 100644 index 7a7b65c..0000000 --- a/admission-controller/init/snippets.js +++ /dev/null @@ -1,84 +0,0 @@ -import k8s from '@kubernetes/client-node'; -import forge from 'node-forge'; - -// ----- -// Read cert from secret -const kc = new k8s.KubeConfig(); -kc.loadFromDefault(); - -const k8sApi = kc.makeApiClient(k8s.CoreV1Api); - -k8sApi.listNamespacedSecret('monokle-admission-controller').then((res) => { - const body = res.body;// as k8s.V1SecretList; - - body.items.forEach((item) => { - // console.log(item.metadata); - // console.log(item.data); - - if (item.data?.['tls.crt']) { - console.log(item.data['tls.crt']); - console.log(Buffer.from(item.data['tls.crt'], 'base64').toString('utf8')) - - const crt = forge.pki.certificateFromPem(Buffer.from(item.data['tls.crt'], 'base64').toString('utf8')); - console.log(crt); - - console.log(crt.validity); - } - }); -}); - -// ----- -// Write cert to secret -const kc = new k8s.KubeConfig(); -kc.loadFromDefault(); - -const k8sApi = kc.makeApiClient(k8s.CoreV1Api); - -k8sApi.createNamespacedSecret('monokle-admission-controller', { - apiVersion: 'v1', - kind: 'Secret', - type: 'kubernetes.io/tls', - metadata: { - name: 'monokle-webhook-server-cert', - }, - data: { - 'tls.crt': Buffer.from('test').toString('base64'), - 'tls.key': Buffer.from('test').toString('base64'), - }, -}).then((res) => { - console.log(res.body); -}); - -// ----- -// Update 'ValidatingWebhookConfiguration' with new cert -const kc = new k8s.KubeConfig(); -kc.loadFromDefault(); - -const k8sApi = kc.makeApiClient(k8s.AdmissionregistrationV1Api); -const client = k8s.KubernetesObjectApi.makeApiClient(kc); - -k8sApi.listValidatingWebhookConfiguration().then((res) => { - // console.log(res.body); - // console.log(res.body.items[0].webhooks); - - const wh = res.body.items[0].webhooks; - wh[0].clientConfig.caBundle = 'test'; - - // https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ - k8sApi.patchValidatingWebhookConfiguration('demo-webhook', { - webhooks: wh, - metadata: { - labels: { - 'monokle.io/updated': Date.now().toString(), - }, - } - }, - undefined, undefined, 'Monokle', undefined, undefined, { - headers: { - 'Content-Type': 'application/merge-patch+json', - }, - }).then((res) => { - console.log(res.body); - console.log(res.body.webhooks); - }); -}); diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index 49c2262..d5ef14b 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -1,105 +1,40 @@ -import fs from 'fs'; -import forge from 'node-forge'; +import k8s from '@kubernetes/client-node'; +import logger from './utils/logger.js'; +import { generateCertificates } from './utils/certificates.js'; +import { getSecretCertificate, applySecretCertificate, updateWebhookCertificate } from './utils/kubernetes.js'; -const keyDir = process.argv[2]; -if (!keyDir) { - console.error('Missing key directory argument'); - process.exit(1); -} +const NAMESPACE = 'monokle-admission-controller'; +const SECRET_NAME = 'monokle-admission-controller-tls'; +const WEBHOOK_NAME = 'demo-webhook'; // monokle-admission-controller-webhook -fs.mkdirSync(keyDir, { mode: 0o700, recursive: true }); -process.chdir(keyDir); +(async () => { + const kc = new k8s.KubeConfig(); + kc.loadFromDefault(); -const caKeys = forge.pki.rsa.generateKeyPair(2048); + const existingCert = await getSecretCertificate(NAMESPACE, SECRET_NAME, kc); -const caCert = forge.pki.createCertificate(); -caCert.publicKey = caKeys.publicKey; -caCert.serialNumber = '01'; -caCert.validity.notBefore = new Date(); -caCert.validity.notAfter = new Date(); -caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); + if (existingCert) { + // @TODO validate existing cert (expiration date) + logger.info('Secret already exists. Skipping.'); + return; + } -const attrs = [ - { name: 'commonName', value: 'Monokle Admission Controller CA' }, -]; -caCert.setSubject(attrs); -caCert.setIssuer(attrs); -caCert.setExtensions([ - { - name: 'basicConstraints', - cA: true, - }, -]); + const certs = generateCertificates(); + const certCreated = await applySecretCertificate(NAMESPACE, SECRET_NAME, certs.serverKey, certs.serverCert, kc); -caCert.sign(caKeys.privateKey); + if (!certCreated) { + logger.error('Failed to create secret'); + return; + } -fs.writeFileSync('ca.key', forge.pki.privateKeyToPem(caKeys.privateKey)); -fs.writeFileSync('ca.crt', forge.pki.certificateToPem(caCert)); + logger.info('Secret created'); -const serverKeys = forge.pki.rsa.generateKeyPair(2048); -const csr = forge.pki.createCertificationRequest(); -csr.publicKey = serverKeys.publicKey; -csr.setSubject([ - { - name: 'commonName', - value: 'webhook-server.monokle-admission-controller.svc', - }, -]); -// This is according to docs here: https://www.npmjs.com/package/node-forge#pkcs10. -// Looks like some types are incorrect. -(csr as any).setAttributes([ - { - name: 'extensionRequest', - extensions: [ - { - name: 'subjectAltName', - altNames: [ - { - type: 2, - value: 'webhook-server.monokle-admission-controller.svc', - }, - ], - }, - ], - }, -]); -csr.sign(serverKeys.privateKey); + const webhookUpdated = updateWebhookCertificate(NAMESPACE, WEBHOOK_NAME, certs.serverCert, kc); -const serverCert = forge.pki.createCertificate(); -serverCert.publicKey = csr.publicKey; -serverCert.serialNumber = '01'; -serverCert.validity.notBefore = new Date(); -serverCert.validity.notAfter = new Date(); -serverCert.validity.notAfter.setFullYear(serverCert.validity.notBefore.getFullYear() + 1); -serverCert.setSubject(csr.subject.attributes); -serverCert.setIssuer(caCert.subject.attributes); -serverCert.setExtensions([ - { - name: 'basicConstraints', - cA: false, - }, - { - name: 'keyUsage', - nonRepudiation: true, - digitalSignature: true, - keyEncipherment: true, - }, - { - name: 'extKeyUsage', - serverAuth: true, - clientAuth: true, - }, - { - name: 'subjectAltName', - altNames: [ - { - type: 2, - value: 'webhook-server.monokle-admission-controller.svc', - }, - ], - }, -]); -serverCert.sign(caKeys.privateKey, forge.md.sha256.create()); + if (!webhookUpdated) { + logger.error('Failed to update webhook'); + return; + } -fs.writeFileSync('webhook-server-tls.key', forge.pki.privateKeyToPem(serverKeys.privateKey)); -fs.writeFileSync('webhook-server-tls.crt', forge.pki.certificateToPem(serverCert)); + logger.info('Webhook updated created'); +})(); diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts new file mode 100644 index 0000000..945c5d9 --- /dev/null +++ b/admission-controller/init/src/utils/certificates.ts @@ -0,0 +1,105 @@ +import forge from 'node-forge'; + +export type CertificateSet = { + caKey: forge.pki.PrivateKey; + caCert: forge.pki.Certificate; + serverKey: forge.pki.PrivateKey; + serverCert: forge.pki.Certificate; +} + +export function generateCertificates(): CertificateSet { + const caKeys = forge.pki.rsa.generateKeyPair(2048); + + const caCert = forge.pki.createCertificate(); + caCert.publicKey = caKeys.publicKey; + caCert.serialNumber = '01'; + caCert.validity.notBefore = new Date(); + caCert.validity.notAfter = new Date(); + caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); + + const attrs = [ + { name: 'commonName', value: 'Monokle Admission Controller CA' }, + ]; + caCert.setSubject(attrs); + caCert.setIssuer(attrs); + caCert.setExtensions([ + { + name: 'basicConstraints', + cA: true, + }, + ]); + + caCert.sign(caKeys.privateKey); + + const serverKeys = forge.pki.rsa.generateKeyPair(2048); + const csr = forge.pki.createCertificationRequest(); + csr.publicKey = serverKeys.publicKey; + csr.setSubject([ + { + name: 'commonName', + value: 'webhook-server.monokle-admission-controller.svc', + }, + ]); + // This is according to docs here: https://www.npmjs.com/package/node-forge#pkcs10. + // Looks like TS typings issue. + (csr as any).setAttributes([ + { + name: 'extensionRequest', + extensions: [ + { + name: 'subjectAltName', + altNames: [ + { + type: 2, + value: 'webhook-server.monokle-admission-controller.svc', + }, + ], + }, + ], + }, + ]); + csr.sign(serverKeys.privateKey); + + const serverCert = forge.pki.createCertificate(); + serverCert.publicKey = csr.publicKey; + serverCert.serialNumber = '01'; + serverCert.validity.notBefore = new Date(); + serverCert.validity.notAfter = new Date(); + serverCert.validity.notAfter.setFullYear(serverCert.validity.notBefore.getFullYear() + 1); + serverCert.setSubject(csr.subject.attributes); + serverCert.setIssuer(caCert.subject.attributes); + serverCert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + }, + { + name: 'keyUsage', + nonRepudiation: true, + digitalSignature: true, + keyEncipherment: true, + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + }, + { + name: 'subjectAltName', + altNames: [ + { + type: 2, + value: 'webhook-server.monokle-admission-controller.svc', + }, + ], + }, + ]); + serverCert.sign(caKeys.privateKey, forge.md.sha256.create()); + + return { + caKey: caKeys.privateKey, + caCert: caCert, + serverKey: serverKeys.privateKey, + serverCert: serverCert, + } +} diff --git a/admission-controller/init/src/utils/kubernetes.ts b/admission-controller/init/src/utils/kubernetes.ts new file mode 100644 index 0000000..f1c5790 --- /dev/null +++ b/admission-controller/init/src/utils/kubernetes.ts @@ -0,0 +1,128 @@ +import k8s from '@kubernetes/client-node'; +import forge from 'node-forge'; +import logger, {formatLog} from './logger.js'; + +export type SecretCertificate = { + secret: k8s.V1Secret; + certificate: forge.pki.Certificate; +} + +export async function getSecretCertificate(namespace: string, name: string, config: k8s.KubeConfig): Promise { + const k8sApi = config.makeApiClient(k8s.CoreV1Api); + + let res; + try { + res = await k8sApi.readNamespacedSecret(name, namespace); + + const secret = res.body; + + // Should not happen, means secret data is invalid/corrupted. + if (!secret.data?.['tls.crt']) { + throw new Error(`Secret ${namespace}/${name} does not contain a tls.crt.`); + } + + const certificate = forge.pki.certificateFromPem(Buffer.from(secret.data['tls.crt'], 'base64').toString('utf8')); + + return { + secret, + certificate + } + } catch (err: any) { + if (err.body?.code === 404) { + logger.info(formatLog(`No existing secret ${namespace}/${name}`, err, res)); + } else { + logger.error(formatLog(`Failed to read secret ${namespace}/${name}`, err, res)); + } + + return null; + } +} + +export async function applySecretCertificate(namespace: string, name: string, pk: forge.pki.PrivateKey, certificate: forge.pki.Certificate, config: k8s.KubeConfig) { + const k8sApi = config.makeApiClient(k8s.CoreV1Api); + + const pemPK = forge.pki.privateKeyToPem(pk); + const pemCert = forge.pki.certificateToPem(certificate); + + try { + await k8sApi.deleteNamespacedSecret(name, namespace); + } catch (err: any) { + logger.debug(formatLog(`Failed to delete secret ${namespace}/${name}`), err); + } + + let res; + try { + res = await k8sApi.createNamespacedSecret(namespace, { + apiVersion: 'v1', + kind: 'Secret', + type: 'kubernetes.io/tls', + metadata: { + name, + }, + data: { + 'tls.crt': Buffer.from(pemCert).toString('base64'), + 'tls.key': Buffer.from(pemPK).toString('base64'), + }, + }); + + if (res.response.statusCode !== 201) { + throw new Error(`Failed to apply secret ${namespace}/${name} (non 201 status code)`); + } + + return true; + } catch (err: any) { + logger.error(formatLog(`Failed to apply secret ${namespace}/${name}`, err, res)); + return false; + } +} + +export async function updateWebhookCertificate(namespace: string, name: string, certificate: forge.pki.Certificate, config: k8s.KubeConfig) { + const k8sApi = config.makeApiClient(k8s.AdmissionregistrationV1Api); + const client = k8s.KubernetesObjectApi.makeApiClient(config); + + let res; + try { + const resRead = res = await client.read({ + apiVersion: 'admissionregistration.k8s.io/v1', + kind: 'ValidatingWebhookConfiguration', + metadata: { + name, + namespace, + }, + }); + + if (resRead.response.statusCode !== 200) { + throw new Error(`Failed to read webhook ${namespace}/${name} (non 200 status code)`); + } + + const webhookConfig = (res.body.webhooks || [])[0]; + if (!webhookConfig) { + throw new Error(`Webhook ${namespace}/${name} does not exist`); + } + + webhookConfig.clientConfig.caBundle = Buffer.from(forge.pki.certificateToPem(certificate)).toString('base64'); + + const resPatch = res = await k8sApi.patchValidatingWebhookConfiguration(name, { + webhooks: [webhookConfig], + metadata: { + labels: { + 'monokle.io/updated': Date.now().toString(), + }, + } + }, + undefined, undefined, 'Monokle', undefined, undefined, { + headers: { + 'Content-Type': 'application/merge-patch+json', + }, + }); + + if (resPatch.response.statusCode !== 200) { + throw new Error(`Failed to patch webhook ${namespace}/${name} (non 200 status code)`); + } + + return true; + } catch (err: any) { + logger.error(formatLog(`Failed to update webhook ${namespace}/${name}`, err, res)); + return false; + } +} diff --git a/admission-controller/init/src/utils/logger.ts b/admission-controller/init/src/utils/logger.ts new file mode 100644 index 0000000..49b6d5c --- /dev/null +++ b/admission-controller/init/src/utils/logger.ts @@ -0,0 +1,26 @@ +import { IncomingMessage } from 'http'; +import pino from 'pino'; + +type FormattedLog = { + msg: string + error: string | undefined + errorDetails: any | undefined + responseStatus: number | undefined + responseMessage: string | undefined +}; + +const logger = pino({ + name: 'Monokle:Init', +}); + +export function formatLog(msg: string, err?: any, res?: {response: IncomingMessage}): FormattedLog { + return { + msg, + error: err?.message, + errorDetails: err?.body, + responseStatus: res?.response?.statusCode, + responseMessage: res?.response?.statusMessage, + } +} + +export default logger; From 97ffc9482ade10129737d713359c92e48a4c3f84 Mon Sep 17 00:00:00 2001 From: f1ames Date: Wed, 11 Oct 2023 14:36:19 +0200 Subject: [PATCH 07/14] fix: use correct cert fo webhook --- admission-controller/init/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index d5ef14b..bb48fd1 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -29,7 +29,7 @@ const WEBHOOK_NAME = 'demo-webhook'; // monokle-admission-controller-webhook logger.info('Secret created'); - const webhookUpdated = updateWebhookCertificate(NAMESPACE, WEBHOOK_NAME, certs.serverCert, kc); + const webhookUpdated = updateWebhookCertificate(NAMESPACE, WEBHOOK_NAME, certs.caCert, kc); if (!webhookUpdated) { logger.error('Failed to update webhook'); From 01e4780b622d2a1ddc9ed3d4425bec5a51f9010e Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 11:49:18 +0200 Subject: [PATCH 08/14] feat: use init container for webhook cert generation --- .gitignore | 2 - Dockerfile | 11 ---- README.md | 2 +- admission-controller/init/Dockerfile | 10 ++++ admission-controller/init/src/index.ts | 19 ++++++- admission-controller/server/Dockerfile | 11 ++++ .../server/src/utils/validation-server.ts | 17 ++++-- .../deployment.yaml} | 40 ++++++++++++- k8s/manifests/namespace.yaml | 4 ++ k8s/manifests/service-account.yaml | 57 +++++++++++++++++-- k8s/skaffold.yaml | 7 ++- k8s/templates/webhook.yaml.template | 29 ---------- scripts/deploy.sh | 57 ++----------------- 13 files changed, 157 insertions(+), 109 deletions(-) delete mode 100644 Dockerfile create mode 100644 admission-controller/init/Dockerfile create mode 100644 admission-controller/server/Dockerfile rename k8s/{templates/deployment.yaml.template => manifests/deployment.yaml} (50%) create mode 100644 k8s/manifests/namespace.yaml delete mode 100644 k8s/templates/webhook.yaml.template diff --git a/.gitignore b/.gitignore index 89f5237..85e7c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ /.idea/ -k8s/manifests/webhook.yaml -k8s/manifests/deployment.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0eb757e..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM node:18.16.0-bullseye-slim - -WORKDIR /workdir -COPY admission-controller/server/package*.json ./ -RUN npm ci --ignore-scripts - -COPY ./admission-controller/server/src ./src -COPY ./admission-controller/server/tsconfig.json ./ - -CMD ["npm", "run", "prod"] -EXPOSE 8443 diff --git a/README.md b/README.md index f1d4bc0..4e31d55 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ minikube start --uuid 00000000-0000-0000-0000-000000000001 --extra-config=apiser Namespaced resources (webhook server) will be deployed to dedicated `monokle-admission-controller` namespace, to watch it you can run: ```bash -watch kubectl -n monokle-admission-controller get all,CustomResourceDefinition,ValidatingWebhookConfiguration,MutatingWebhookConfiguration +watch kubectl -n monokle-admission-controller get all,CustomResourceDefinition,ValidatingWebhookConfiguration,secrets ``` After it runs, the result should be something like: diff --git a/admission-controller/init/Dockerfile b/admission-controller/init/Dockerfile new file mode 100644 index 0000000..53de456 --- /dev/null +++ b/admission-controller/init/Dockerfile @@ -0,0 +1,10 @@ +FROM node:18.16.0-bullseye-slim + +WORKDIR /workdir +COPY package*.json ./ +RUN npm ci --ignore-scripts + +COPY ./src ./src +COPY ./tsconfig.json ./ + +CMD ["npm", "run", "prod"] diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index bb48fd1..81d676a 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -7,9 +7,22 @@ const NAMESPACE = 'monokle-admission-controller'; const SECRET_NAME = 'monokle-admission-controller-tls'; const WEBHOOK_NAME = 'demo-webhook'; // monokle-admission-controller-webhook +// @TODO Rework flow +// 1. Fetch webhook data +// - Do not make sense to continue if there is no webhook +// 2. Fetch secret data +// 3. Check secret validity +// - If empty or invalid, generate new secret +// 4. Write cert to webhook +// 5. Write cert to secret +// +// Such order prevents from cases when secret is updated but webhook is not + (async () => { const kc = new k8s.KubeConfig(); - kc.loadFromDefault(); + kc.loadFromCluster(); + + sleep(1000 * 10); const existingCert = await getSecretCertificate(NAMESPACE, SECRET_NAME, kc); @@ -38,3 +51,7 @@ const WEBHOOK_NAME = 'demo-webhook'; // monokle-admission-controller-webhook logger.info('Webhook updated created'); })(); + +async function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/admission-controller/server/Dockerfile b/admission-controller/server/Dockerfile new file mode 100644 index 0000000..0333e30 --- /dev/null +++ b/admission-controller/server/Dockerfile @@ -0,0 +1,11 @@ +FROM node:18.16.0-bullseye-slim + +WORKDIR /workdir +COPY package*.json ./ +RUN npm ci --ignore-scripts + +COPY ./src ./src +COPY ./tsconfig.json ./ + +CMD ["npm", "run", "prod"] +EXPOSE 8443 diff --git a/admission-controller/server/src/utils/validation-server.ts b/admission-controller/server/src/utils/validation-server.ts index 2bd4508..df16fa0 100644 --- a/admission-controller/server/src/utils/validation-server.ts +++ b/admission-controller/server/src/utils/validation-server.ts @@ -69,12 +69,17 @@ export class ValidationServer { ) { this._shouldValidate = false; - this._server = fastify({ - https: { - key: readFileSync(path.join('/run/secrets/tls', 'tls.key')), - cert: readFileSync(path.join('/run/secrets/tls', 'tls.crt')) - } - }); + try { + this._server = fastify({ + https: { + key: readFileSync(path.join('/run/secrets/tls', 'tls.key')), + cert: readFileSync(path.join('/run/secrets/tls', 'tls.crt')) + } + }); + } catch (err) { + this._logger.error({msg: 'Failed to read TLS certificate', err}); + process.exit(1); + } this._initRouting(); } diff --git a/k8s/templates/deployment.yaml.template b/k8s/manifests/deployment.yaml similarity index 50% rename from k8s/templates/deployment.yaml.template rename to k8s/manifests/deployment.yaml index 51ee4d3..41b2ff2 100644 --- a/k8s/templates/deployment.yaml.template +++ b/k8s/manifests/deployment.yaml @@ -18,6 +18,9 @@ spec: # securityContext: # runAsNonRoot: true # runAsUser: 1000 + initContainers: + - name: init + image: admission-webhook-init containers: - name: server image: admission-webhook @@ -37,8 +40,9 @@ spec: volumes: - name: webhook-tls-certs secret: - secretName: webhook-server-tls - serviceAccountName: monokle-policies + secretName: monokle-admission-controller-tls + optional: true + serviceAccountName: monokle-policies-sa --- apiVersion: v1 kind: Service @@ -50,4 +54,34 @@ spec: app: webhook-server ports: - port: 443 - targetPort: webhook-api \ No newline at end of file + targetPort: webhook-api +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: demo-webhook +webhooks: + - name: webhook-server.monokle-admission-controller.svc + sideEffects: None + admissionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook-server + namespace: monokle-admission-controller + path: "/validate" + caBundle: "" + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-node-lease + - kube-public + - kube-system + - monokle-admission-controller + rules: + - operations: ["CREATE", "UPDATE"] + apiGroups: ["*"] + apiVersions: ["*"] + resources: ["*"] + scope: "*" \ No newline at end of file diff --git a/k8s/manifests/namespace.yaml b/k8s/manifests/namespace.yaml new file mode 100644 index 0000000..9a18955 --- /dev/null +++ b/k8s/manifests/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monokle-admission-controller diff --git a/k8s/manifests/service-account.yaml b/k8s/manifests/service-account.yaml index 492b1b4..5033f49 100644 --- a/k8s/manifests/service-account.yaml +++ b/k8s/manifests/service-account.yaml @@ -1,7 +1,9 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: monokle-policies + name: monokle-policies-sa + namespace: monokle-admission-controller + --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -11,7 +13,6 @@ rules: - apiGroups: ["monokle.com"] resources: ["policies", "policybindings"] verbs: ["list", "watch"] - --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 @@ -19,9 +20,57 @@ metadata: name: monokle-policies subjects: - kind: ServiceAccount - name: monokle-policies + name: monokle-policies-sa namespace: monokle-admission-controller roleRef: kind: ClusterRole name: monokle-policies - apiGroup: rbac.authorization.k8s.io \ No newline at end of file + apiGroup: rbac.authorization.k8s.io + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: monokle-policies-init-vwc +rules: +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations"] + verbs: ["get", "patch"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: monokle-policies-init-vwc +subjects: +- kind: ServiceAccount + name: monokle-policies-sa + namespace: monokle-admission-controller +roleRef: + kind: ClusterRole + name: monokle-policies-init-vwc + apiGroup: rbac.authorization.k8s.io + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: monokle-policies-init-secrets + namespace: monokle-admission-controller +rules: +- apiGroups: ["*"] # should be "" as this is a core group + resources: ["secrets"] + verbs: ["get", "create", "delete"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: monokle-policies-init-secrets + namespace: monokle-admission-controller +subjects: +- kind: ServiceAccount + name: monokle-policies-sa + namespace: monokle-admission-controller +roleRef: + kind: Role + name: monokle-policies-init-secrets + apiGroup: rbac.authorization.k8s.io diff --git a/k8s/skaffold.yaml b/k8s/skaffold.yaml index db22fb8..cb3981e 100644 --- a/k8s/skaffold.yaml +++ b/k8s/skaffold.yaml @@ -5,8 +5,13 @@ metadata: build: artifacts: - image: admission-webhook + context: ./admission-controller/server docker: - dockerfile: Dockerfile + dockerfile: ./admission-controller/server/Dockerfile + - image: admission-webhook-init + context: ./admission-controller/init + docker: + dockerfile: ./admission-controller/init/Dockerfile manifests: rawYaml: - ./k8s/manifests/deployment.yaml diff --git a/k8s/templates/webhook.yaml.template b/k8s/templates/webhook.yaml.template deleted file mode 100644 index 32d61ac..0000000 --- a/k8s/templates/webhook.yaml.template +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: demo-webhook -webhooks: - - name: webhook-server.monokle-admission-controller.svc - sideEffects: None - admissionReviewVersions: ["v1", "v1beta1"] - clientConfig: - service: - name: webhook-server - namespace: monokle-admission-controller - path: "/validate" - caBundle: ${CA_PEM_B64} - namespaceSelector: - matchExpressions: - - key: kubernetes.io/metadata.name - operator: NotIn - values: - - kube-node-lease - - kube-public - - kube-system - - monokle-admission-controller - rules: - - operations: ["CREATE", "UPDATE"] - apiGroups: ["*"] - apiVersions: ["*"] - resources: ["*"] - scope: "*" \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 02e51cc..fedc44b 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,68 +1,23 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 StackRox Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 +#!/bin/bash # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# deploy.sh -# -# Sets up the environment for the admission controller webhook in the active cluster. +# Sets up environment for admission controller webhook in the active cluster. Use for development/testing only. set -euo pipefail basedir="$(dirname "$0")" -keydir="$(mktemp -d)" -templdir="${basedir}/../k8s/templates" resdir="${basedir}/../k8s/manifests" -# Generate keys into a temporary directory. -echo "Generating TLS keys with node ..." -node "${basedir}/../admission-controller/init/dist/index.js" "$keydir" - -# Create the `monokle-admission-controller` namespace. This cannot be part of the YAML file as we first need to create the TLS secret, -# which would fail otherwise. -echo "Creating Kubernetes objects ..." -kubectl create namespace monokle-admission-controller - # Create test namespaces kubectl create namespace nstest1 kubectl create namespace nstest2 -# Create the TLS secret for the generated keys. -kubectl -n monokle-admission-controller create secret tls webhook-server-tls \ - --cert "${keydir}/webhook-server-tls.crt" \ - --key "${keydir}/webhook-server-tls.key" - -rm -f "${resdir}/webhook.yaml" "${resdir}/deployment.yaml" - -# Read the PEM-encoded CA certificate, base64 encode it, and replace the `${CA_PEM_B64}` placeholder in the YAML -# template with it. Then, create the Kubernetes resources. -ca_pem_b64="$(openssl base64 -A <"${keydir}/ca.crt")" -sed -e 's@${CA_PEM_B64}@'"$ca_pem_b64"'@g' <"${templdir}/webhook.yaml.template" > "${resdir}/webhook.yaml" -cp "${templdir}/deployment.yaml.template" "${resdir}/deployment.yaml" - -# Cluster-wide +# Apply monokle-admission-controller releated resources +kubectl apply -f "${resdir}/namespace.yaml" kubectl apply -f "${resdir}/monokle-policy-crd.yaml" kubectl apply -f "${resdir}/monokle-policy-binding-crd.yaml" +kubectl apply -f "${resdir}/service-account.yaml" -# Namespaced -kubectl apply -f "${resdir}/service-account.yaml" -n monokle-admission-controller - +# Run deployment through skaffold with locally build images skaffold run -n monokle-admission-controller -f k8s/skaffold.yaml -sleep 5 -kubectl apply -f "${resdir}/webhook.yaml" - -# Delete the key directory to prevent abuse (DO NOT USE THESE KEYS ANYWHERE ELSE). -rm -rf "$keydir" echo "The webhook server has been deployed and configured!" From 654f01ae29fbd8de730f308e36892a6fc3fe19cc Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 11:59:15 +0200 Subject: [PATCH 09/14] chore: cleanup project strucutre and namings --- .gitignore | 66 +++++- LICENSE | 222 ++---------------- README.md | 20 +- admission-controller/init/.gitignore | 65 ----- .../init/src/utils/certificates.ts | 6 +- admission-controller/server/.gitignore | 65 ----- admission-controller/server/src/index.ts | 4 +- examples/policy-binding-sample-1.yaml | 2 +- examples/policy-binding-sample-2.yaml | 2 +- examples/policy-binding-sample-3.yaml | 2 +- examples/policy-sample-1.yaml | 2 +- examples/policy-sample-2.yaml | 2 +- k8s/manifests/deployment.yaml | 20 +- k8s/manifests/monokle-policy-binding-crd.yaml | 4 +- k8s/manifests/monokle-policy-crd.yaml | 4 +- k8s/manifests/service-account.yaml | 2 +- k8s/skaffold.yaml | 4 +- scripts/deploy.sh | 2 +- 18 files changed, 122 insertions(+), 372 deletions(-) delete mode 100644 admission-controller/init/.gitignore delete mode 100644 admission-controller/server/.gitignore diff --git a/.gitignore b/.gitignore index 85e7c1d..f4cefe8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,65 @@ -/.idea/ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# 0x +profile-* + +# mac files +.DS_Store + +# vim swap files +*.swp + +# webstorm +.idea + +# vscode +.vscode +*code-workspace + +# clinic +profile* +*clinic* +*flamegraph* + +# generated code +examples/typescript-server.js +test/types/index.js + +# compiled app +dist diff --git a/LICENSE b/LICENSE index 261eeb9..25f0b6a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2023 kubeshop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4e31d55..b3f89a3 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,20 @@ After it runs, the result should be something like: ```bash NAME READY STATUS RESTARTS AGE -pod/webhook-server-7cd54c9fcf-wdkdn 0/1 Error 1 (13s ago) 25s +pod/monokle-admission-controller-server-7cd54c9fcf-wdkdn 0/1 Error 1 (13s ago) 25s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/webhook-server ClusterIP 10.97.66.194 443/TCP 25s +service/monokle-admission-controller-server ClusterIP 10.97.66.194 443/TCP 25s NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/webhook-server 0/1 1 0 25s +deployment.apps/monokle-admission-controller-server 0/1 1 0 25s NAME DESIRED CURRENT READY AGE -replicaset.apps/webhook-server-7cd54c9fcf 1 1 0 25s +replicaset.apps/monokle-admission-controller-server-7cd54c9fcf 1 1 0 25s NAME CREATED AT -customresourcedefinition.apiextensions.k8s.io/policies.monokle.com 2023-10-02T12:17:02Z -customresourcedefinition.apiextensions.k8s.io/policybindings.monokle.com 2023-10-02T12:17:02Z +customresourcedefinition.apiextensions.k8s.io/policies.monokle.io 2023-10-02T12:17:02Z +customresourcedefinition.apiextensions.k8s.io/policybindings.monokle.io 2023-10-02T12:17:02Z NAME WEBHOOKS AGE validatingwebhookconfiguration.admissionregistration.k8s.io/demo-webhook 1 17s @@ -60,8 +60,8 @@ For getting info about CRDs: ```bash kubectl get crd -kubectl describe crd policies.monokle.com -kubectl describe crd policybindings.monokle.com +kubectl describe crd policies.monokle.io +kubectl describe crd policybindings.monokle.io ``` #### Testing @@ -117,8 +117,8 @@ kubectl delete validatingwebhookconfiguration.admissionregistration.k8s.io/demo- kubectl delete namespace monokle-admission-controller && \ kubectl delete namespace nstest1 && \ kubectl delete namespace nstest2 && \ -kubectl delete crd policies.monokle.com && \ -kubectl delete crd policybindings.monokle.com +kubectl delete crd policies.monokle.io && \ +kubectl delete crd policybindings.monokle.io ``` ### Refs diff --git a/admission-controller/init/.gitignore b/admission-controller/init/.gitignore deleted file mode 100644 index f4cefe8..0000000 --- a/admission-controller/init/.gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* - -# generated code -examples/typescript-server.js -test/types/index.js - -# compiled app -dist diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts index 945c5d9..9190afe 100644 --- a/admission-controller/init/src/utils/certificates.ts +++ b/admission-controller/init/src/utils/certificates.ts @@ -37,7 +37,7 @@ export function generateCertificates(): CertificateSet { csr.setSubject([ { name: 'commonName', - value: 'webhook-server.monokle-admission-controller.svc', + value: 'monokle-admission-controller-server.monokle-admission-controller.svc', }, ]); // This is according to docs here: https://www.npmjs.com/package/node-forge#pkcs10. @@ -51,7 +51,7 @@ export function generateCertificates(): CertificateSet { altNames: [ { type: 2, - value: 'webhook-server.monokle-admission-controller.svc', + value: 'monokle-admission-controller-server.monokle-admission-controller.svc', }, ], }, @@ -89,7 +89,7 @@ export function generateCertificates(): CertificateSet { altNames: [ { type: 2, - value: 'webhook-server.monokle-admission-controller.svc', + value: 'monokle-admission-controller-server.monokle-admission-controller.svc', }, ], }, diff --git a/admission-controller/server/.gitignore b/admission-controller/server/.gitignore deleted file mode 100644 index f4cefe8..0000000 --- a/admission-controller/server/.gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# 0x -profile-* - -# mac files -.DS_Store - -# vim swap files -*.swp - -# webstorm -.idea - -# vscode -.vscode -*code-workspace - -# clinic -profile* -*clinic* -*flamegraph* - -# generated code -examples/typescript-server.js -test/types/index.js - -# compiled app -dist diff --git a/admission-controller/server/src/index.ts b/admission-controller/server/src/index.ts index cdad7e7..15dbf22 100644 --- a/admission-controller/server/src/index.ts +++ b/admission-controller/server/src/index.ts @@ -14,7 +14,7 @@ const logger = pino({ (async() => { const policyInformer = await getInformer( - 'monokle.com', + 'monokle.io', 'v1alpha1', 'policies', (err: any) => { @@ -23,7 +23,7 @@ const logger = pino({ ); const bindingsInformer = await getInformer( - 'monokle.com', + 'monokle.io', 'v1alpha1', 'policybindings', (err: any) => { diff --git a/examples/policy-binding-sample-1.yaml b/examples/policy-binding-sample-1.yaml index 13136e0..984780a 100644 --- a/examples/policy-binding-sample-1.yaml +++ b/examples/policy-binding-sample-1.yaml @@ -1,4 +1,4 @@ -apiVersion: monokle.com/v1alpha1 +apiVersion: monokle.io/v1alpha1 kind: MonoklePolicyBinding metadata: name: policy-binding-sample-1 diff --git a/examples/policy-binding-sample-2.yaml b/examples/policy-binding-sample-2.yaml index 9a945ba..3c883c2 100644 --- a/examples/policy-binding-sample-2.yaml +++ b/examples/policy-binding-sample-2.yaml @@ -1,4 +1,4 @@ -apiVersion: monokle.com/v1alpha1 +apiVersion: monokle.io/v1alpha1 kind: MonoklePolicyBinding metadata: name: policy-binding-sample-2 diff --git a/examples/policy-binding-sample-3.yaml b/examples/policy-binding-sample-3.yaml index 1f8a5e1..da5c918 100644 --- a/examples/policy-binding-sample-3.yaml +++ b/examples/policy-binding-sample-3.yaml @@ -1,4 +1,4 @@ -apiVersion: monokle.com/v1alpha1 +apiVersion: monokle.io/v1alpha1 kind: MonoklePolicyBinding metadata: name: policy-binding-sample-3 diff --git a/examples/policy-sample-1.yaml b/examples/policy-sample-1.yaml index 7030db2..15fd702 100644 --- a/examples/policy-sample-1.yaml +++ b/examples/policy-sample-1.yaml @@ -1,4 +1,4 @@ -apiVersion: monokle.com/v1alpha1 +apiVersion: monokle.io/v1alpha1 kind: MonoklePolicy metadata: name: policy-sample-1 diff --git a/examples/policy-sample-2.yaml b/examples/policy-sample-2.yaml index 2c15f5f..e9905fa 100644 --- a/examples/policy-sample-2.yaml +++ b/examples/policy-sample-2.yaml @@ -1,4 +1,4 @@ -apiVersion: monokle.com/v1alpha1 +apiVersion: monokle.io/v1alpha1 kind: MonoklePolicy metadata: name: policy-sample-2 diff --git a/k8s/manifests/deployment.yaml b/k8s/manifests/deployment.yaml index 41b2ff2..8fac1b5 100644 --- a/k8s/manifests/deployment.yaml +++ b/k8s/manifests/deployment.yaml @@ -1,30 +1,26 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: webhook-server + name: monokle-admission-controller-server namespace: monokle-admission-controller labels: - app: webhook-server + app: monokle-admission-controller-server spec: replicas: 1 selector: matchLabels: - app: webhook-server + app: monokle-admission-controller-server template: metadata: labels: - app: webhook-server + app: monokle-admission-controller-server spec: - # securityContext: - # runAsNonRoot: true - # runAsUser: 1000 initContainers: - name: init image: admission-webhook-init containers: - name: server image: admission-webhook - #imagePullPolicy: Always ports: - containerPort: 8443 name: webhook-api @@ -47,11 +43,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: webhook-server + name: monokle-admission-controller-server namespace: monokle-admission-controller spec: selector: - app: webhook-server + app: monokle-admission-controller-server ports: - port: 443 targetPort: webhook-api @@ -61,12 +57,12 @@ kind: ValidatingWebhookConfiguration metadata: name: demo-webhook webhooks: - - name: webhook-server.monokle-admission-controller.svc + - name: monokle-admission-controller-server.monokle-admission-controller.svc sideEffects: None admissionReviewVersions: ["v1", "v1beta1"] clientConfig: service: - name: webhook-server + name: monokle-admission-controller-server namespace: monokle-admission-controller path: "/validate" caBundle: "" diff --git a/k8s/manifests/monokle-policy-binding-crd.yaml b/k8s/manifests/monokle-policy-binding-crd.yaml index 138ca6a..92a0c6b 100644 --- a/k8s/manifests/monokle-policy-binding-crd.yaml +++ b/k8s/manifests/monokle-policy-binding-crd.yaml @@ -1,9 +1,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: policybindings.monokle.com + name: policybindings.monokle.io spec: - group: monokle.com + group: monokle.io versions: - name: v1alpha1 served: true diff --git a/k8s/manifests/monokle-policy-crd.yaml b/k8s/manifests/monokle-policy-crd.yaml index 709ddad..31fabad 100644 --- a/k8s/manifests/monokle-policy-crd.yaml +++ b/k8s/manifests/monokle-policy-crd.yaml @@ -1,9 +1,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: policies.monokle.com + name: policies.monokle.io spec: - group: monokle.com + group: monokle.io versions: - name: v1alpha1 served: true diff --git a/k8s/manifests/service-account.yaml b/k8s/manifests/service-account.yaml index 5033f49..c6eb3c8 100644 --- a/k8s/manifests/service-account.yaml +++ b/k8s/manifests/service-account.yaml @@ -10,7 +10,7 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: monokle-policies rules: -- apiGroups: ["monokle.com"] +- apiGroups: ["monokle.io"] resources: ["policies", "policybindings"] verbs: ["list", "watch"] --- diff --git a/k8s/skaffold.yaml b/k8s/skaffold.yaml index cb3981e..ea4135c 100644 --- a/k8s/skaffold.yaml +++ b/k8s/skaffold.yaml @@ -1,7 +1,7 @@ apiVersion: skaffold/v4beta5 kind: Config metadata: - name: webhook-server + name: monokle-admission-controller-server build: artifacts: - image: admission-webhook @@ -17,5 +17,5 @@ manifests: - ./k8s/manifests/deployment.yaml portForward: - resourceType: service - resourceName: webhook-server + resourceName: monokle-admission-controller-server port: 443 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index fedc44b..922f25f 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -20,4 +20,4 @@ kubectl apply -f "${resdir}/service-account.yaml" # Run deployment through skaffold with locally build images skaffold run -n monokle-admission-controller -f k8s/skaffold.yaml -echo "The webhook server has been deployed and configured!" +echo "Deployment complete." From e88a2ce4a2f9269bf7a1b0a3802f44053b4f4702 Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 12:08:24 +0200 Subject: [PATCH 10/14] fix: fix role api groups for secrets --- k8s/manifests/service-account.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/manifests/service-account.yaml b/k8s/manifests/service-account.yaml index c6eb3c8..636fdbb 100644 --- a/k8s/manifests/service-account.yaml +++ b/k8s/manifests/service-account.yaml @@ -57,7 +57,7 @@ metadata: name: monokle-policies-init-secrets namespace: monokle-admission-controller rules: -- apiGroups: ["*"] # should be "" as this is a core group +- apiGroups: [""] resources: ["secrets"] verbs: ["get", "create", "delete"] --- From 3ceafc9d06a0d06bfe7416ec3bdec7a3f01b2b08 Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 14:11:16 +0200 Subject: [PATCH 11/14] refactor: improve cert generation flow --- README.md | 4 +- admission-controller/init/package-lock.json | 33 ++++++++ admission-controller/init/package.json | 2 + admission-controller/init/src/index.ts | 75 ++++++++++--------- .../init/src/utils/certificates.ts | 5 ++ .../init/src/utils/kubernetes.ts | 25 +++++-- k8s/manifests/deployment.yaml | 2 +- 7 files changed, 102 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index b3f89a3..14c5b89 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ customresourcedefinition.apiextensions.k8s.io/policies.monokle.io 2023-1 customresourcedefinition.apiextensions.k8s.io/policybindings.monokle.io 2023-10-02T12:17:02Z NAME WEBHOOKS AGE -validatingwebhookconfiguration.admissionregistration.k8s.io/demo-webhook 1 17s +validatingwebhookconfiguration.admissionregistration.k8s.io/monokle-admission-controller-webhook 1 17s ``` For getting info about CRDs: @@ -113,7 +113,7 @@ You can also do manual clean-up and re-run `./deploy.sh` script again: ```bash kubectl delete all -n monokle-admission-controller --all && \ -kubectl delete validatingwebhookconfiguration.admissionregistration.k8s.io/demo-webhook -n monokle-admission-controller && \ +kubectl delete validatingwebhookconfiguration.admissionregistration.k8s.io/monokle-admission-controller-webhook && \ kubectl delete namespace monokle-admission-controller && \ kubectl delete namespace nstest1 && \ kubectl delete namespace nstest2 && \ diff --git a/admission-controller/init/package-lock.json b/admission-controller/init/package-lock.json index 1510815..4f4e01c 100644 --- a/admission-controller/init/package-lock.json +++ b/admission-controller/init/package-lock.json @@ -10,10 +10,12 @@ "license": "ISC", "dependencies": { "@kubernetes/client-node": "^0.18.1", + "async-retry": "^1.3.3", "node-forge": "^1.3.1", "pino": "^8.16.0" }, "devDependencies": { + "@types/async-retry": "^1.4.6", "@types/node": "^20.1.7", "@types/node-forge": "^1.3.6", "typescript": "^4.6.3" @@ -50,6 +52,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.4.tgz", "integrity": "sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==" }, + "node_modules/@types/async-retry": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.4.6.tgz", + "integrity": "sha512-or8JPgYUtyPpO0ddHImwUWmSjVE/UalxgMm2d0r3698QhjzlM7eke0PT60bOxs1NG7HxU232RQ1vy1iQKGGRTw==", + "dev": true, + "dependencies": { + "@types/retry": "*" + } + }, "node_modules/@types/caseless": { "version": "0.12.3", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.3.tgz", @@ -85,6 +96,12 @@ "form-data": "^2.5.0" } }, + "node_modules/@types/retry": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.3.tgz", + "integrity": "sha512-rkxEZUFIyDEZhC6EfHz6Hwos2zXewCOLBzhdgv7D55qu4OAySNwDZzxbaMpFI6XthdBa5oHhR5s6/9MSuTfw4g==", + "dev": true + }, "node_modules/@types/tough-cookie": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", @@ -145,6 +162,14 @@ "node": ">=0.8" } }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -878,6 +903,14 @@ "node": ">= 0.12" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/rfc4648": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.2.tgz", diff --git a/admission-controller/init/package.json b/admission-controller/init/package.json index 3eea333..e5de3d5 100644 --- a/admission-controller/init/package.json +++ b/admission-controller/init/package.json @@ -13,10 +13,12 @@ "license": "ISC", "dependencies": { "@kubernetes/client-node": "^0.18.1", + "async-retry": "^1.3.3", "node-forge": "^1.3.1", "pino": "^8.16.0" }, "devDependencies": { + "@types/async-retry": "^1.4.6", "@types/node": "^20.1.7", "@types/node-forge": "^1.3.6", "typescript": "^4.6.3" diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index 81d676a..1cea0de 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -1,57 +1,64 @@ import k8s from '@kubernetes/client-node'; -import logger from './utils/logger.js'; -import { generateCertificates } from './utils/certificates.js'; -import { getSecretCertificate, applySecretCertificate, updateWebhookCertificate } from './utils/kubernetes.js'; +import retry from 'async-retry'; +import logger, { formatLog } from './utils/logger.js'; +import { generateCertificates, isCertValid } from './utils/certificates.js'; +import { getSecretCertificate, applySecretCertificate, getWebhookConfiguration, patchWebhookCertificate } from './utils/kubernetes.js'; const NAMESPACE = 'monokle-admission-controller'; const SECRET_NAME = 'monokle-admission-controller-tls'; -const WEBHOOK_NAME = 'demo-webhook'; // monokle-admission-controller-webhook +const WEBHOOK_NAME = 'monokle-admission-controller-webhook'; -// @TODO Rework flow -// 1. Fetch webhook data -// - Do not make sense to continue if there is no webhook -// 2. Fetch secret data -// 3. Check secret validity +(async () => { + await retry(run, { + retries: 5, + factor: 3, + onRetry: (err, attempt) => { + logger.error(formatLog(`Cert init attempt ${attempt} failed. Retrying...`, err)); + } + }); +})(); + +// The flow is as follows: +// +// 1. Fetch webhook resource. +// - If there is none, it doesn't make sense to continue. Throw an error and retry. +// 2. Fetch secret data (containing our cert). +// 3. Check cert validity. +// - If valid, exit. // - If empty or invalid, generate new secret -// 4. Write cert to webhook -// 5. Write cert to secret +// 4. Write cert to webhook. +// 5. Write cert to secret. // -// Such order prevents from cases when secret is updated but webhook is not - -(async () => { +// Such order of actions prevents from cases where secret (with) cert is updated but webhook is not. +// At the same time, entire process is treated as atomic one, if something goes wrong, retry from the beginning. +async function run(_bail: (e: Error) => void, _attempt: number) { const kc = new k8s.KubeConfig(); kc.loadFromCluster(); - sleep(1000 * 10); + const webhookConfig = await getWebhookConfiguration(NAMESPACE, WEBHOOK_NAME, kc); + if (!webhookConfig) { + throw new Error(`Webhook ${NAMESPACE}/${WEBHOOK_NAME} does not exist.`); + } const existingCert = await getSecretCertificate(NAMESPACE, SECRET_NAME, kc); - - if (existingCert) { - // @TODO validate existing cert (expiration date) - logger.info('Secret already exists. Skipping.'); + if (existingCert && isCertValid(existingCert.certificate)) { + logger.info('Valid cert already exists.'); return; } const certs = generateCertificates(); - const certCreated = await applySecretCertificate(NAMESPACE, SECRET_NAME, certs.serverKey, certs.serverCert, kc); - if (!certCreated) { - logger.error('Failed to create secret'); - return; + const webhookPatched = patchWebhookCertificate(NAMESPACE, WEBHOOK_NAME, webhookConfig, certs.caCert, kc); + if (!webhookPatched) { + throw new Error('Failed to update webhook.'); } - logger.info('Secret created'); + logger.info('Webhook patched successfully.'); - const webhookUpdated = updateWebhookCertificate(NAMESPACE, WEBHOOK_NAME, certs.caCert, kc); - - if (!webhookUpdated) { - logger.error('Failed to update webhook'); - return; + const certCreated = await applySecretCertificate(NAMESPACE, SECRET_NAME, certs.serverKey, certs.serverCert, kc); + if (!certCreated) { + throw new Error('Failed to create secret.'); } - logger.info('Webhook updated created'); -})(); - -async function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + logger.info('Secret created successfully.'); } diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts index 9190afe..1451135 100644 --- a/admission-controller/init/src/utils/certificates.ts +++ b/admission-controller/init/src/utils/certificates.ts @@ -103,3 +103,8 @@ export function generateCertificates(): CertificateSet { serverCert: serverCert, } } + +export function isCertValid(certificate: forge.pki.Certificate): boolean { + const now = new Date(); + return now > certificate.validity.notBefore && now < certificate.validity.notAfter; +} diff --git a/admission-controller/init/src/utils/kubernetes.ts b/admission-controller/init/src/utils/kubernetes.ts index f1c5790..73c8856 100644 --- a/admission-controller/init/src/utils/kubernetes.ts +++ b/admission-controller/init/src/utils/kubernetes.ts @@ -76,13 +76,12 @@ export async function applySecretCertificate(namespace: string, name: string, pk } } -export async function updateWebhookCertificate(namespace: string, name: string, certificate: forge.pki.Certificate, config: k8s.KubeConfig) { - const k8sApi = config.makeApiClient(k8s.AdmissionregistrationV1Api); +export async function getWebhookConfiguration(namespace: string, name: string, config: k8s.KubeConfig): Promise { const client = k8s.KubernetesObjectApi.makeApiClient(config); let res; try { - const resRead = res = await client.read({ + res = await client.read({ apiVersion: 'admissionregistration.k8s.io/v1', kind: 'ValidatingWebhookConfiguration', metadata: { @@ -91,11 +90,23 @@ export async function updateWebhookCertificate(namespace: string, name: string, }, }); - if (resRead.response.statusCode !== 200) { - throw new Error(`Failed to read webhook ${namespace}/${name} (non 200 status code)`); + if (res.response.statusCode !== 200) { + throw new Error(`Failed to get webhook ${namespace}/${name} (non 200 status code)`); } - const webhookConfig = (res.body.webhooks || [])[0]; + return res.body; + } catch (err: any) { + logger.error(formatLog(`Failed to get webhook ${namespace}/${name}`, err, res)); + return null; + } +} + +export async function patchWebhookCertificate(namespace: string, name: string, webhook: k8s.V1ValidatingWebhookConfiguration, certificate: forge.pki.Certificate, config: k8s.KubeConfig) { + const k8sApi = config.makeApiClient(k8s.AdmissionregistrationV1Api); + + let res; + try { + const webhookConfig = (webhook.webhooks || [])[0]; if (!webhookConfig) { throw new Error(`Webhook ${namespace}/${name} does not exist`); } @@ -122,7 +133,7 @@ export async function updateWebhookCertificate(namespace: string, name: string, return true; } catch (err: any) { - logger.error(formatLog(`Failed to update webhook ${namespace}/${name}`, err, res)); + logger.error(formatLog(`Failed to patch webhook ${namespace}/${name}`, err, res)); return false; } } diff --git a/k8s/manifests/deployment.yaml b/k8s/manifests/deployment.yaml index 8fac1b5..e3f9935 100644 --- a/k8s/manifests/deployment.yaml +++ b/k8s/manifests/deployment.yaml @@ -55,7 +55,7 @@ spec: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: demo-webhook + name: monokle-admission-controller-webhook webhooks: - name: monokle-admission-controller-server.monokle-admission-controller.svc sideEffects: None From 7f2666b9f56da869f4b4eff8c2fd158c78b0f2f8 Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 14:19:06 +0200 Subject: [PATCH 12/14] refactor: get rid of unnecessary CSR from cert generation --- .../init/src/utils/certificates.ts | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts index 1451135..5a80338 100644 --- a/admission-controller/init/src/utils/certificates.ts +++ b/admission-controller/init/src/utils/certificates.ts @@ -32,41 +32,18 @@ export function generateCertificates(): CertificateSet { caCert.sign(caKeys.privateKey); const serverKeys = forge.pki.rsa.generateKeyPair(2048); - const csr = forge.pki.createCertificationRequest(); - csr.publicKey = serverKeys.publicKey; - csr.setSubject([ - { - name: 'commonName', - value: 'monokle-admission-controller-server.monokle-admission-controller.svc', - }, - ]); - // This is according to docs here: https://www.npmjs.com/package/node-forge#pkcs10. - // Looks like TS typings issue. - (csr as any).setAttributes([ - { - name: 'extensionRequest', - extensions: [ - { - name: 'subjectAltName', - altNames: [ - { - type: 2, - value: 'monokle-admission-controller-server.monokle-admission-controller.svc', - }, - ], - }, - ], - }, - ]); - csr.sign(serverKeys.privateKey); - const serverCert = forge.pki.createCertificate(); - serverCert.publicKey = csr.publicKey; + serverCert.publicKey = serverKeys.publicKey; serverCert.serialNumber = '01'; serverCert.validity.notBefore = new Date(); serverCert.validity.notAfter = new Date(); serverCert.validity.notAfter.setFullYear(serverCert.validity.notBefore.getFullYear() + 1); - serverCert.setSubject(csr.subject.attributes); + serverCert.setSubject([ + { + name: 'commonName', + value: 'monokle-admission-controller-server.monokle-admission-controller.svc', + }, + ]); serverCert.setIssuer(caCert.subject.attributes); serverCert.setExtensions([ { From 1ad8dfe7341ad01e7899e0bedde285bc4f927553 Mon Sep 17 00:00:00 2001 From: f1ames Date: Thu, 12 Oct 2023 15:08:44 +0200 Subject: [PATCH 13/14] refactor: apply minor code/docs corrections --- README.md | 44 ++++++++++++------- admission-controller/init/src/index.ts | 4 +- .../init/src/utils/certificates.ts | 4 +- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 14c5b89..c3509a3 100644 --- a/README.md +++ b/README.md @@ -36,24 +36,29 @@ watch kubectl -n monokle-admission-controller get all,CustomResourceDefinition,V After it runs, the result should be something like: ```bash -NAME READY STATUS RESTARTS AGE -pod/monokle-admission-controller-server-7cd54c9fcf-wdkdn 0/1 Error 1 (13s ago) 25s +NAME READY STATUS RESTARTS AGE +pod/monokle-admission-controller-server-6958c9bbf8-jvkvk 1/1 Running 0 5m11s -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/monokle-admission-controller-server ClusterIP 10.97.66.194 443/TCP 25s +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/monokle-admission-controller-server ClusterIP 10.99.122.106 443/TCP 5m11s -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/monokle-admission-controller-server 0/1 1 0 25s +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/monokle-admission-controller-server 1/1 1 1 5m11s -NAME DESIRED CURRENT READY AGE -replicaset.apps/monokle-admission-controller-server-7cd54c9fcf 1 1 0 25s +NAME DESIRED CURRENT READY AGE +replicaset.apps/monokle-admission-controller-server-6958c9bbf8 1 1 1 5m11s -NAME CREATED AT -customresourcedefinition.apiextensions.k8s.io/policies.monokle.io 2023-10-02T12:17:02Z -customresourcedefinition.apiextensions.k8s.io/policybindings.monokle.io 2023-10-02T12:17:02Z +NAME CREATED AT +customresourcedefinition.apiextensions.k8s.io/policies.monokle.io 2023-10-12T12:16:04Z +customresourcedefinition.apiextensions.k8s.io/policybindings.monokle.io 2023-10-12T12:16:04Z -NAME WEBHOOKS AGE -validatingwebhookconfiguration.admissionregistration.k8s.io/monokle-admission-controller-webhook 1 17s +NAME WEBHOOKS AGE +validatingwebhookconfiguration.admissionregistration.k8s.io/monokle-admission-controller-webhook 1 5m11s + +NAME TYPE DATA AGE +secret/default-token-w56nz kubernetes.io/service-account-token 3 5m39s +secret/monokle-admission-controller-tls kubernetes.io/tls 2 5m1s +secret/monokle-policies-sa-token-fcpld kubernetes.io/service-account-token 3 5m49s ``` For getting info about CRDs: @@ -64,6 +69,14 @@ kubectl describe crd policies.monokle.io kubectl describe crd policybindings.monokle.io ``` +##### Init container logs + +The `monokle-admission-controller-server` has one init container which is responsible for certificate creation/renewal and propagation into cluster. Logs from it can be viewed with: + +```bash +kubectl -n monokle-admission-controller logs pod/monokle-admission-controller-server-... -c init +``` + #### Testing First you need to create policy resource, for example: @@ -107,8 +120,6 @@ After everything is running you can allow hot-reload by running: skaffold dev -f k8s/skaffold.yaml ``` -**Important**: Skaffold will recreate deployment on every change so make sure that webhook pod doesn't get rejected by it's previous version (via Validation Admission Controller). - You can also do manual clean-up and re-run `./deploy.sh` script again: ```bash @@ -129,3 +140,6 @@ kubectl delete crd policybindings.monokle.io * https://minikube.sigs.k8s.io/docs/tutorials/using_psp/ * https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/ * https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ +* https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +* https://kubernetes-client.github.io/javascript/index.html +* https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/ diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index 1cea0de..47b5bc0 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -25,11 +25,11 @@ const WEBHOOK_NAME = 'monokle-admission-controller-webhook'; // 2. Fetch secret data (containing our cert). // 3. Check cert validity. // - If valid, exit. -// - If empty or invalid, generate new secret +// - If empty or invalid, generate new secret. // 4. Write cert to webhook. // 5. Write cert to secret. // -// Such order of actions prevents from cases where secret (with) cert is updated but webhook is not. +// Such order of actions prevents from cases where secret (with cert) is updated but webhook is not. // At the same time, entire process is treated as atomic one, if something goes wrong, retry from the beginning. async function run(_bail: (e: Error) => void, _attempt: number) { const kc = new k8s.KubeConfig(); diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts index 5a80338..23f0812 100644 --- a/admission-controller/init/src/utils/certificates.ts +++ b/admission-controller/init/src/utils/certificates.ts @@ -15,7 +15,7 @@ export function generateCertificates(): CertificateSet { caCert.serialNumber = '01'; caCert.validity.notBefore = new Date(); caCert.validity.notAfter = new Date(); - caCert.validity.notAfter.setFullYear(caCert.validity.notBefore.getFullYear() + 1); + caCert.validity.notAfter.setMonth(caCert.validity.notBefore.getMonth() + 3); const attrs = [ { name: 'commonName', value: 'Monokle Admission Controller CA' }, @@ -37,7 +37,7 @@ export function generateCertificates(): CertificateSet { serverCert.serialNumber = '01'; serverCert.validity.notBefore = new Date(); serverCert.validity.notAfter = new Date(); - serverCert.validity.notAfter.setFullYear(serverCert.validity.notBefore.getFullYear() + 1); + serverCert.validity.notAfter.setMonth(serverCert.validity.notBefore.getMonth() + 3); serverCert.setSubject([ { name: 'commonName', From bc5bd3c4571beeac767225b13cace05a66de5e7c Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 17 Oct 2023 10:15:18 +0200 Subject: [PATCH 14/14] chore: improve cert expiration handling --- admission-controller/init/src/index.ts | 10 +++++----- admission-controller/init/src/utils/certificates.ts | 12 +++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/admission-controller/init/src/index.ts b/admission-controller/init/src/index.ts index 47b5bc0..e456215 100644 --- a/admission-controller/init/src/index.ts +++ b/admission-controller/init/src/index.ts @@ -1,7 +1,7 @@ import k8s from '@kubernetes/client-node'; import retry from 'async-retry'; import logger, { formatLog } from './utils/logger.js'; -import { generateCertificates, isCertValid } from './utils/certificates.js'; +import { generateCertificates, isCertExpiring, isCertValid } from './utils/certificates.js'; import { getSecretCertificate, applySecretCertificate, getWebhookConfiguration, patchWebhookCertificate } from './utils/kubernetes.js'; const NAMESPACE = 'monokle-admission-controller'; @@ -24,8 +24,8 @@ const WEBHOOK_NAME = 'monokle-admission-controller-webhook'; // - If there is none, it doesn't make sense to continue. Throw an error and retry. // 2. Fetch secret data (containing our cert). // 3. Check cert validity. -// - If valid, exit. -// - If empty or invalid, generate new secret. +// - If valid and not within 3 months till expiration, exit. +// - If empty, invalid or close to expire (within 3 months), generate new secret. // 4. Write cert to webhook. // 5. Write cert to secret. // @@ -41,12 +41,12 @@ async function run(_bail: (e: Error) => void, _attempt: number) { } const existingCert = await getSecretCertificate(NAMESPACE, SECRET_NAME, kc); - if (existingCert && isCertValid(existingCert.certificate)) { + if (existingCert && isCertValid(existingCert.certificate) && !isCertExpiring(existingCert.certificate, 90)) { logger.info('Valid cert already exists.'); return; } - const certs = generateCertificates(); + const certs = generateCertificates(NAMESPACE, 6); const webhookPatched = patchWebhookCertificate(NAMESPACE, WEBHOOK_NAME, webhookConfig, certs.caCert, kc); if (!webhookPatched) { diff --git a/admission-controller/init/src/utils/certificates.ts b/admission-controller/init/src/utils/certificates.ts index 23f0812..dce76eb 100644 --- a/admission-controller/init/src/utils/certificates.ts +++ b/admission-controller/init/src/utils/certificates.ts @@ -7,7 +7,7 @@ export type CertificateSet = { serverCert: forge.pki.Certificate; } -export function generateCertificates(): CertificateSet { +export function generateCertificates(namespace: string, expireInMonths: number): CertificateSet { const caKeys = forge.pki.rsa.generateKeyPair(2048); const caCert = forge.pki.createCertificate(); @@ -15,7 +15,7 @@ export function generateCertificates(): CertificateSet { caCert.serialNumber = '01'; caCert.validity.notBefore = new Date(); caCert.validity.notAfter = new Date(); - caCert.validity.notAfter.setMonth(caCert.validity.notBefore.getMonth() + 3); + caCert.validity.notAfter.setMonth(caCert.validity.notBefore.getMonth() + expireInMonths); const attrs = [ { name: 'commonName', value: 'Monokle Admission Controller CA' }, @@ -37,7 +37,7 @@ export function generateCertificates(): CertificateSet { serverCert.serialNumber = '01'; serverCert.validity.notBefore = new Date(); serverCert.validity.notAfter = new Date(); - serverCert.validity.notAfter.setMonth(serverCert.validity.notBefore.getMonth() + 3); + serverCert.validity.notAfter.setMonth(serverCert.validity.notBefore.getMonth() + expireInMonths); serverCert.setSubject([ { name: 'commonName', @@ -81,6 +81,12 @@ export function generateCertificates(): CertificateSet { } } +export function isCertExpiring(certificate: forge.pki.Certificate, thresholdDays: number): boolean { + const threshold = new Date(); + threshold.setDate(threshold.getDate() + thresholdDays); + return threshold > certificate.validity.notAfter; +} + export function isCertValid(certificate: forge.pki.Certificate): boolean { const now = new Date(); return now > certificate.validity.notBefore && now < certificate.validity.notAfter;