From 46c465dce6ad718a65dba8d371d0be5670865346 Mon Sep 17 00:00:00 2001 From: MohammedAbdi Date: Mon, 10 Jun 2024 14:26:28 -0400 Subject: [PATCH 01/39] upgrade-version-1.8 Signed-off-by: MohammedAbdi --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a20e2d82..804a616d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v1.7.0 +v1.8.0 From 3e4ef3c76a376b21faab307eb5520866a823ae18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:55:14 +0000 Subject: [PATCH 02/39] Bump actions/checkout from 4.1.6 to 4.1.7 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.6...v4.1.7) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- .github/workflows/master-build.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b9cbbb92..113d57ba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: EOF - name: Check out repository code - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Install Golang uses: actions/setup-go@v5.0.1 diff --git a/.github/workflows/master-build.yaml b/.github/workflows/master-build.yaml index b70daa04..bf8deb35 100644 --- a/.github/workflows/master-build.yaml +++ b/.github/workflows/master-build.yaml @@ -19,7 +19,7 @@ jobs: EOF - name: Check out repository code - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Log in to GHCR.io uses: docker/login-action@v3.2.0 @@ -60,7 +60,7 @@ jobs: EOF - name: Check out repository code - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 - name: Log in to GHCR.io uses: docker/login-action@v3.2.0 with: From 2befe78c5046746e0565b0efaf34dca511ca5cdd Mon Sep 17 00:00:00 2001 From: MohammedAbdi Date: Mon, 10 Jun 2024 14:49:34 -0400 Subject: [PATCH 03/39] Update contributing doc Signed-off-by: MohammedAbdi --- CONTRIBUTING.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d23bfb18..e331f2eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,9 @@ Please see [CONTRIBUTING](https://github.com/spiffe/spiffe/blob/main/CONTRIBUTING.md) and [GOVERNANCE](https://github.com/spiffe/spiffe/blob/main/GOVERNANCE.md) from the SPIFFE project. +> [!IMPORTANT] +> Before opening a new issue or PR, search for any existing issues [here](https://github.com/spiffe/tornjak/issues) to avoid duplication. + ## Pre-built images You can use pre-built images for various versions and Tornjak components. For a list of supported public images of Tornjak along with usage instructions please see our [USAGE document](./USAGE.md). @@ -39,7 +42,40 @@ For usage instructions of the containers, please see our [USAGE document](./USAG ## Development -We welcome all development attempst and contributions from the community. The easiest place to start is by reviewing our code architecture diagrams available in our [api documentation](./docs/tornjak-ui-api-documentation.md#11-overview). +We welcome all development attempt and contributions from the community. The easiest place to start is by reviewing our code architecture diagrams available in our [api documentation](./docs/tornjak-ui-api-documentation.md#11-overview). + +## Opening a pull request + +1. Fork/ clone the tornjak repo +2. Ensure your branch is based on the latest commit in `dev` +3. Commit changes to your fork +4. Make sure your commit messages contain a `Signed-off-by: ` line (see `git-commit --signoff`) to certify the [DCO](/DCO) +5. Open a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) + against the upstream `dev` branch + +> [!IMPORTANT] +> Please make sure you open all PRs against the `dev` branch + +> [!IMPORTANT] +> For any new feature design, or feature level changes, please create an issue 1st, then submit a PR with design details before code implementation. + +## After your pull request is submitted + +At least one maintainer must approve the pull request. + +Once your pull request is submitted, it's your responsibility to: + +* Respond to reviewer's feedback +* Keep it merge-ready at all times until it has been approved and actually merged + +Following approval, the pull request will be merged by the last maintainer to approve the request. + +#### Third-party code + +When third-party code must be included, all licenses must be preserved. This includes modified +third-party code and excerpts, as well. + +Thank you for contributing to Tornjak! ## Local testing From a7b2e936baf72aa49c60aa18380606954367eecb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:26:37 +0000 Subject: [PATCH 04/39] Bump braces from 3.0.2 to 3.0.3 in /frontend Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 65b68ef7..e4505862 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6890,11 +6890,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -10596,9 +10596,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, From 14a738ffac492d58988a483cbbec1e01ef277d7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:17:18 +0000 Subject: [PATCH 05/39] Bump ws and puppeteer in /frontend Bumps [ws](https://github.com/websockets/ws) to 8.17.1 and updates ancestor dependency [puppeteer](https://github.com/puppeteer/puppeteer). These dependencies need to be updated together. Updates `ws` from 8.16.0 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1) Updates `puppeteer` from 20.9.0 to 22.11.2 - [Release notes](https://github.com/puppeteer/puppeteer/releases) - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/release-please-config.json) - [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v20.9.0...puppeteer-v22.11.2) --- updated-dependencies: - dependency-name: ws dependency-type: indirect - dependency-name: puppeteer dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 533 ++++++++++++++++++++----------------- frontend/package.json | 2 +- 2 files changed, 294 insertions(+), 241 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e4505862..aad4bc50 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -69,7 +69,7 @@ "jest-transform-stub": "^2.0.0", "moxios": "^0.4.0", "nodemon": "^2.0.13", - "puppeteer": "^20.1.2", + "puppeteer": "^22.11.2", "react-inject-env": "^2.1.0", "react-test-renderer": "^18.2.0", "redux-mock-store": "^1.5.4" @@ -4316,32 +4316,25 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", "dev": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "yargs": "17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/@puppeteer/browsers/node_modules/cliui": { @@ -4358,10 +4351,43 @@ "node": ">=12" } }, + "node_modules/@puppeteer/browsers/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -6428,9 +6454,9 @@ } }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "node_modules/babel-jest": { @@ -6724,6 +6750,52 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6744,9 +6816,9 @@ ] }, "node_modules/basic-ftp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", - "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -7299,12 +7371,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.23.tgz", + "integrity": "sha512-1o/gLU9wDqbN5nL2MtfjykjOuighGXc3/hnWueO1haiEoFgX8h5vbvcA4tgdQfjw1mkZ1OEF4x/+HVeqEX6NoA==", "dev": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" @@ -8645,9 +8719,9 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, "node_modules/data-uri-to-buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", - "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "engines": { "node": ">= 14" @@ -8921,9 +8995,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", "dev": true }, "node_modules/didyoumean": { @@ -9248,6 +9322,15 @@ "node": ">= 6" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/enzyme": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", @@ -10879,17 +10962,6 @@ "node": ">=8" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -10951,14 +11023,6 @@ "node": ">=6" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -11011,17 +11075,17 @@ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" } }, "node_modules/fs-monkey": { @@ -11150,15 +11214,15 @@ } }, "node_modules/get-uri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", - "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dev": true, "dependencies": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.0", + "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "fs-extra": "^11.2.0" }, "engines": { "node": ">= 14" @@ -11921,10 +11985,23 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ipaddr.js": { @@ -14527,6 +14604,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -14642,10 +14725,12 @@ } }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -15211,15 +15296,9 @@ } }, "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, "node_modules/moment": { @@ -15850,9 +15929,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -15862,9 +15941,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -15875,9 +15954,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -15888,13 +15967,12 @@ } }, "node_modules/pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "dependencies": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" }, "engines": { @@ -17444,28 +17522,28 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -17475,9 +17553,9 @@ } }, "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -17488,9 +17566,9 @@ } }, "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -17544,72 +17622,53 @@ } }, "node_modules/puppeteer": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-20.9.0.tgz", - "integrity": "sha512-kAglT4VZ9fWEGg3oLc4/de+JcONuEJhlh3J6f5R1TLkrY/EHHIHxWXDOzXvaxQCtedmyVXBwg8M+P8YCO/wZjw==", - "deprecated": "< 21.3.7 is no longer supported", + "version": "22.11.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.11.2.tgz", + "integrity": "sha512-8fjdQSgW0sq7471ftca24J7sXK+jXZ7OW7Gx+NEBFNyXrcTiBfukEI46gNq6hiMhbLEDT30NeylK/1ZoPdlKSA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "cosmiconfig": "8.2.0", - "puppeteer-core": "20.9.0" + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.11.2" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" }, "engines": { - "node": ">=16.3.0" + "node": ">=18" } }, "node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "version": "22.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.11.2.tgz", + "integrity": "sha512-vQo+YDuePyvj+92Z9cdtxi/HalKf+k/R4tE80nGtQqJRNqU81eHaHkbVfnLszdaLlvwFF5tipnnSCzqWlEddtw==", "dev": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.5.23", + "debug": "4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "8.17.1" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/puppeteer-core/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" + "ms": "2.1.2" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "supports-color": { "optional": true } } @@ -17621,21 +17680,29 @@ "dev": true }, "node_modules/puppeteer/node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/puppeteer/node_modules/js-yaml": { @@ -18269,17 +18336,6 @@ "node": ">=12" } }, - "node_modules/react-scripts/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/react-scripts/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -18305,14 +18361,6 @@ "node": ">=10" } }, - "node_modules/react-scripts/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/react-scripts/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -19616,26 +19664,26 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, @@ -19644,9 +19692,9 @@ } }, "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -19655,12 +19703,6 @@ "node": ">= 14" } }, - "node_modules/socks/node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true - }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -19904,13 +19946,17 @@ } }, "node_modules/streamx": { - "version": "2.15.6", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", - "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { @@ -20399,20 +20445,23 @@ } }, "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", "dev": true, "dependencies": { - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { "b4a": "^1.6.4", @@ -20539,6 +20588,15 @@ "node": ">=8" } }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -21012,12 +21070,11 @@ } }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/unpipe": { @@ -21088,6 +21145,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/use-resize-observer": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-6.1.0.tgz", @@ -21808,17 +21871,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "node_modules/workbox-build/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/workbox-build/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -21843,14 +21895,6 @@ "node": ">=4" } }, - "node_modules/workbox-build/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/workbox-build/node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -22127,9 +22171,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, @@ -22225,6 +22269,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 04938e82..610aedc8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -98,7 +98,7 @@ "jest-transform-stub": "^2.0.0", "moxios": "^0.4.0", "nodemon": "^2.0.13", - "puppeteer": "^20.1.2", + "puppeteer": "^22.11.2", "react-inject-env": "^2.1.0", "react-test-renderer": "^18.2.0", "redux-mock-store": "^1.5.4" From 70b6506493f1d70e596fe59376b25432f0000de2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 18 Jun 2024 16:44:08 -0400 Subject: [PATCH 06/39] Make dropdown open Signed-off-by: unknown Signed-off-by: Xuhang Cao --- docs/quickstart/README.md | 72 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/quickstart/README.md b/docs/quickstart/README.md index 9676e0f1..923f4ad9 100644 --- a/docs/quickstart/README.md +++ b/docs/quickstart/README.md @@ -6,12 +6,12 @@ Before we dive into the deployment process, let’s familiarize ourselves with T [SPIRE](https://github.com/spiffe/spire) (the SPIFFE Runtime Environment) is an open-source software tool that provides a way to issue and manage identities in the form of SPIFFE IDs within a distributed system. These identities are used to establish trust between software services and are based on the SPIFFE (Secure Production Identity Framework For Everyone) standards, which define a universal identity control plane for distributed systems. SPIRE provides access to the SPIFFE Workload API, which authenticates active software systems and allocates SPIFFE IDs and corresponding SVIDs to them. This process enables mutual trust establishment between two distinct workloads. -Tornjak is a control plane and GUI for SPIRE, aimed at managing SPIRE deployments across multiple clusters. It provides a management plane that simplifies and centralizes the administration of SPIRE, offering an intuitive interface for defining, distributing, and visualizing SPIFFE identities across a heterogeneous environment. +Tornjak is a control plane and GUI for SPIRE, aimed at managing SPIRE deployments across multiple clusters. It provides a management plane that simplifies and centralizes the administration of SPIRE, offering an intuitive interface for defining, distributing, and visualizing SPIFFE identities across a heterogeneous environment. -This tutorial will get you up and running with a local deployment of SPIRE and Tornjak in three simple steps: +This tutorial will get you up and running with a local deployment of SPIRE and Tornjak in three simple steps: - Setting up the deployment files - Deployment -- Connecting to Tornjak. +- Connecting to Tornjak. Contents - [Step 0: Prerequisite](#step-0-prerequisite) @@ -21,7 +21,7 @@ Contents - [Cleanup](#cleanup) - [Troubleshooting Commmon Issues](#Troubleshooting) -## Step 0: Prerequisite +## Step 0: Prerequisite Before you begin this tutorial, make sure you have the following: - Minikube: Version 1.12.0 or later. [Download Minikube.](https://minikube.sigs.k8s.io/docs/start/) @@ -29,13 +29,13 @@ Before you begin this tutorial, make sure you have the following: Note: While we have tested this tutorial with the versions below, newer versions should also work. Ensure you're using the most recent stable releases to avoid compatibility issues. - Minikube Version 1.12.0, Version 1.31.2 - - Docker Version 20.10.23, Version 24.0.6 + - Docker Version 20.10.23, Version 24.0.6 ## Step 1: Setup deployment files ### Setting up k8s -For this tutorial, we will use minikube. If you have an existing kubernetes cluster, feel free to use that. +For this tutorial, we will use minikube. If you have an existing kubernetes cluster, feel free to use that. ```console minikube start @@ -74,11 +74,11 @@ cd tornjak cd docs/quickstart ``` -Notice, the files in this directory are largely the same files as provided by the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/). However, there are some minor key differences. Take note of the tornjak-configmap.yaml file, which includes configuration details for the Tornjak backend. +Notice, the files in this directory are largely the same files as provided by the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/). However, there are some minor key differences. Take note of the tornjak-configmap.yaml file, which includes configuration details for the Tornjak backend. To view the configuration you can issue the following: ```console -cat tornjak-configmap.yaml +cat tornjak-configmap.yaml ``` Contents of the configuration for the Tornjak backend should look like: @@ -114,24 +114,24 @@ data: } ``` -More information on this config file format can be found in [our config documentation](../config-tornjak-server.md). +More information on this config file format can be found in [our config documentation](../config-tornjak-server.md). -Additionally, we have sample server-statefulset files in the directory `server-statefulset-examples`. We will copy one of them in depending on which deployment scheme you would like. +Additionally, we have sample server-statefulset files in the directory `server-statefulset-examples`. We will copy one of them in depending on which deployment scheme you would like. ### Choosing the Statefulset Deployment -Depending on your use case, you can deploy Tornjak in different configurations. Note we have deprecated support of the use case where parts of Tornjak run on the same container as SPIRE. +Depending on your use case, you can deploy Tornjak in different configurations. Note we have deprecated support of the use case where parts of Tornjak run on the same container as SPIRE. -Currently, we support the following deployment scheme: +Currently, we support the following deployment scheme: -1. Only the Tornjak backend (to make Tornjak API calls) is run as a separate container on the same pod that exposes only one port (to communicate with the Tornjak backend). This deployment type is fully-supported, has a smaller sidecar image without the frontend components, and ensures that the frontend and backend share no memory. +1. Only the Tornjak backend (to make Tornjak API calls) is run as a separate container on the same pod that exposes only one port (to communicate with the Tornjak backend). This deployment type is fully-supported, has a smaller sidecar image without the frontend components, and ensures that the frontend and backend share no memory. -Using the option below, easily copy in the right server-statefulset file. +Using the option below, easily copy in the right server-statefulset file. -
🔴 [Click] For the deployment of only the Tornjak backend (API) +
🔴 [Click] For the deployment of only the Tornjak backend (API) -There is an additional requirement to mount the SPIRE server socket and make it accessible to the Tornjak backend container. +There is an additional requirement to mount the SPIRE server socket and make it accessible to the Tornjak backend container. The relevant file is called `backend-sidecar-server-statefulset.yaml` within the examples directory. Please copy to the relevant file as follows: @@ -139,10 +139,10 @@ The relevant file is called `backend-sidecar-server-statefulset.yaml` within the cp server-statefulset-examples/backend-sidecar-server-statefulset.yaml server-statefulset.yaml ``` -The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: +The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: ```console -cat server-statefulset.yaml +cat server-statefulset.yaml ``` ``` @@ -243,11 +243,11 @@ spec: Note that there are three key differences in this StatefulSet file from that in the SPIRE quickstart: -1. There is a new container in the pod named tornjak-backend. +1. There is a new container in the pod named tornjak-backend. 3. We create a volume named `tornjak-config` that reads from the ConfigMap `tornjak-agent`. -4. We create a volume named `test-socket` so that the containers may communicate. +4. We create a volume named `test-socket` so that the containers may communicate. -This is all done specifically to pass the Tornjak config file as an argument to the container and to allow communication between Tornjak and SPIRE. +This is all done specifically to pass the Tornjak config file as an argument to the container and to allow communication between Tornjak and SPIRE.
@@ -255,7 +255,7 @@ This is all done specifically to pass the Tornjak config file as an argument to Now that we have the correct deployment files, please follow the below steps to deploy Tornjak and SPIRE! -NOTE: In a Windows OS environment, you will need to replace the backslashes ( \\ ) below with backticks ( \` ) to copy and paste into a Windows terminal. This doesnt apply for Mac. +NOTE: In a Windows OS environment, you will need to replace the backslashes ( \\ ) below with backticks ( \` ) to copy and paste into a Windows terminal. This doesnt apply for Mac. ```console kubectl apply -f spire-namespace.yaml \ -f server-account.yaml \ @@ -286,7 +286,7 @@ service/tornjak-backend-mtls created service/tornjak-frontend created ``` -Before continuing, check that the spire-server is ready: +Before continuing, check that the spire-server is ready: ```console kubectl get statefulset --namespace spire @@ -301,7 +301,7 @@ NOTE: You may initially see a `0/1` for READY status. Just wait a few minutes an ### Deploying the agent and creating test entries -The following steps will configure and deploy the SPIRE agent. +The following steps will configure and deploy the SPIRE agent. NOTE: In a windows environment, you will need to replace the backslashes ( \\ ) below with backticks ( \` ) to copy and paste into a windows terminal ```console kubectl apply \ @@ -329,7 +329,7 @@ spire-agent 1 1 1 1 1 ``` -Then, we can create a registration entry for the node. +Then, we can create a registration entry for the node. NOTE: In a windows environment, you will need to replace the backslashes ( \\ ) below with backticks ( \` ) to copy and paste into a windows terminal ```console @@ -375,7 +375,7 @@ Selector : k8s:ns:default Selector : k8s:sa:default ``` -Finally, here we deploy a workload container: +Finally, here we deploy a workload container: ```console kubectl apply -f client-deployment.yaml @@ -419,7 +419,7 @@ Should yield two lines depending on which deployment you used: Image: ``` -where `` is `ghcr.io/spiffe/tornjak:latest` if you deployed the Tornjak with the UI and is `ghcr.io/spiffe/tornjak-backend:latest` if you deployed only the Tornjak backend. +where `` is `ghcr.io/spiffe/tornjak:latest` if you deployed the Tornjak with the UI and is `ghcr.io/spiffe/tornjak-backend:latest` if you deployed only the Tornjak backend. ## Step 3: Configuring Access to Tornjak @@ -438,7 +438,7 @@ Forwarding from 127.0.0.1:10000 -> 10000 Forwarding from [::1]:10000 -> 10000 ``` -While this runs, open a browser to +While this runs, open a browser to ``` http://localhost:10000/api/tornjak/serverinfo @@ -450,16 +450,16 @@ This output represents the backend response. Now you should be able to make Torn ### Step 3b: Connecting to the Tornjak frontend to access the Tornjak UI -Make sure that the backend is accessible from your browser at `http://localhost:10000`, as above, or the frontend will not work. +Make sure that the backend is accessible from your browser at `http://localhost:10000`, as above, or the frontend will not work. If you chose to deploy Tornjak with the UI, connecting to the UI is very simple. Otherwise, you can always run the UI locally and connect. See the two choices below:
🔴 [Click] Run the Tornjak frontend locally -You will need to deploy the separate frontend separately to access the exposed Tornjak backend. We have prebuilt the frontend in a container, so we can simply run it via a single docker command in a separate terminal, which will take a couple minutes to run: +You will need to deploy the separate frontend separately to access the exposed Tornjak backend. We have prebuilt the frontend in a container, so we can simply run it via a single docker command in a separate terminal, which will take a couple minutes to run: ```console -docker run -p 3000:3000 -e REACT_APP_API_SERVER_URI='http://localhost:10000' ghcr.io/spiffe/tornjak-frontend:latest +docker run -p 3000:3000 -e REACT_APP_API_SERVER_URI='http://localhost:10000' ghcr.io/spiffe/tornjak-frontend:latest ``` After the image is downloaded, you will eventually see the following output: @@ -469,7 +469,7 @@ After the image is downloaded, you will eventually see the following output: > react-scripts --openssl-legacy-provider start ℹ 「wds」: Project is running at http://172.17.0.3/ -ℹ 「wds」: webpack output is served from +ℹ 「wds」: webpack output is served from ℹ 「wds」: Content not from webpack is served from /usr/src/app/public ℹ 「wds」: 404s will fallback to / Starting the development server... @@ -485,7 +485,7 @@ Note that the development build is not optimized. To create a production build, use npm run build. ``` -Note, it will likely take a few minutes for the applicaiton to compile successfully. +Note, it will likely take a few minutes for the applicaiton to compile successfully.
@@ -516,7 +516,7 @@ kubectl delete clusterrole spire-server-trust-role spire-agent-cluster-role kubectl delete clusterrolebinding spire-server-trust-role-binding spire-agent-cluster-role-binding ``` -## Troubleshooting +## Troubleshooting
Troubleshoot 1: Minikube fails to start with a Docker CLI context error When running the `minikube start` command, you might encounter an error like the one below: @@ -541,9 +541,9 @@ Solution: - Make sure Docker is installed on your system. If it's not installed, you can install Docker by following the instructions on the official Docker [installation guide.](https://docs.docker.com/get-docker/) 2. Start Docker: -- On macOS and Windows: Docker Desktop has a graphical interface to manage the Docker service. Open Docker Desktop to start Docker. Alternativly, run the command '''open -a Docker'''' +- On macOS and Windows: Docker Desktop has a graphical interface to manage the Docker service. Open Docker Desktop to start Docker. Alternativly, run the command '''open -a Docker'''' 3. Retry Starting Minikube: -- After ensuring that Docker is running, you can start Minikube again using: +- After ensuring that Docker is running, you can start Minikube again using: ```console minikube start ``` From caed9ec20e8b1149ccb8074a3c26543c554c618a Mon Sep 17 00:00:00 2001 From: Xuhang Cao Date: Fri, 28 Jun 2024 21:42:10 -0400 Subject: [PATCH 07/39] open dropdown Signed-off-by: Xuhang Cao --- docs/quickstart/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart/README.md b/docs/quickstart/README.md index 923f4ad9..b8852d01 100644 --- a/docs/quickstart/README.md +++ b/docs/quickstart/README.md @@ -454,7 +454,7 @@ Make sure that the backend is accessible from your browser at `http://localhost: If you chose to deploy Tornjak with the UI, connecting to the UI is very simple. Otherwise, you can always run the UI locally and connect. See the two choices below: -
🔴 [Click] Run the Tornjak frontend locally +
🔴 [Click] Run the Tornjak frontend locally You will need to deploy the separate frontend separately to access the exposed Tornjak backend. We have prebuilt the frontend in a container, so we can simply run it via a single docker command in a separate terminal, which will take a couple minutes to run: From 2a35ec22c2afb3f17ef50facc1e5d2d9d08e07cf Mon Sep 17 00:00:00 2001 From: Xuhang Cao Date: Fri, 5 Jul 2024 20:07:06 -0400 Subject: [PATCH 08/39] added versioning and plurality Signed-off-by: Xuhang Cao --- api/agent/server.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index 3d647f3f..67fe169a 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -431,7 +431,7 @@ func (s *Server) verificationMiddleware(next http.Handler) http.Handler { cors(w, r) return } - + userInfo := s.Authenticator.AuthenticateRequest(r) err := s.Authorizer.AuthorizeRequest(r, userInfo) @@ -574,17 +574,26 @@ func (s *Server) GetRouter() http.Handler { // SPIRE server healthcheck apiRtr.HandleFunc("/api/debugserver", s.debugServer) apiRtr.HandleFunc("/api/healthcheck", s.healthcheck) + apiRtr.HandleFunc("/api/v1/debugserver", s.debugServer) + apiRtr.HandleFunc("/api/v1/healthcheck", s.healthcheck) // Agents apiRtr.HandleFunc("/api/agent/list", s.agentList) apiRtr.HandleFunc("/api/agent/ban", s.agentBan) apiRtr.HandleFunc("/api/agent/delete", s.agentDelete) apiRtr.HandleFunc("/api/agent/createjointoken", s.agentCreateJoinToken) + apiRtr.HandleFunc("/api/v1/agents/list", s.agentList) + apiRtr.HandleFunc("/api/v1/agents/ban", s.agentBan) + apiRtr.HandleFunc("/api/v1/agents/delete", s.agentDelete) + apiRtr.HandleFunc("/api/v1/agents/createjointoken", s.agentCreateJoinToken) // Entries apiRtr.HandleFunc("/api/entry/list", s.entryList) apiRtr.HandleFunc("/api/entry/create", s.entryCreate) apiRtr.HandleFunc("/api/entry/delete", s.entryDelete) + apiRtr.HandleFunc("/api/v1/entries/list", s.entryList) + apiRtr.HandleFunc("/api/v1/entries/create", s.entryCreate) + apiRtr.HandleFunc("/api/v1/entries/delete", s.entryDelete) // Tornjak specific apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo) @@ -636,7 +645,7 @@ func (s *Server) HandleRequests() { // TODO: replace with workerGroup for thread safety errChannel := make(chan error, 2) - + serverConfig := s.TornjakConfig.Server if serverConfig.HTTPConfig == nil { err = fmt.Errorf("HTTP Config error: no port configured") From 0acce4ab0b7f0767d6a0528a2cfd18bfd23f00a0 Mon Sep 17 00:00:00 2001 From: Xuhang Cao Date: Tue, 9 Jul 2024 14:20:39 -0400 Subject: [PATCH 09/39] updates with spire and adjustments Signed-off-by: Xuhang Cao --- api/agent/server.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index 67fe169a..15f74e97 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -574,26 +574,28 @@ func (s *Server) GetRouter() http.Handler { // SPIRE server healthcheck apiRtr.HandleFunc("/api/debugserver", s.debugServer) apiRtr.HandleFunc("/api/healthcheck", s.healthcheck) - apiRtr.HandleFunc("/api/v1/debugserver", s.debugServer) - apiRtr.HandleFunc("/api/v1/healthcheck", s.healthcheck) // Agents apiRtr.HandleFunc("/api/agent/list", s.agentList) apiRtr.HandleFunc("/api/agent/ban", s.agentBan) apiRtr.HandleFunc("/api/agent/delete", s.agentDelete) apiRtr.HandleFunc("/api/agent/createjointoken", s.agentCreateJoinToken) - apiRtr.HandleFunc("/api/v1/agents/list", s.agentList) - apiRtr.HandleFunc("/api/v1/agents/ban", s.agentBan) - apiRtr.HandleFunc("/api/v1/agents/delete", s.agentDelete) - apiRtr.HandleFunc("/api/v1/agents/createjointoken", s.agentCreateJoinToken) // Entries apiRtr.HandleFunc("/api/entry/list", s.entryList) apiRtr.HandleFunc("/api/entry/create", s.entryCreate) apiRtr.HandleFunc("/api/entry/delete", s.entryDelete) - apiRtr.HandleFunc("/api/v1/entries/list", s.entryList) - apiRtr.HandleFunc("/api/v1/entries/create", s.entryCreate) - apiRtr.HandleFunc("/api/v1/entries/delete", s.entryDelete) + + // Spire APIs with versioning + apiRtr.HandleFunc("/api/v1/spire/debugserver", s.debugServer) + apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck) + apiRtr.HandleFunc("/api/v1/spire/agents/list", s.agentList) + apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan) + apiRtr.HandleFunc("/api/v1/spire/agents/delete", s.agentDelete) + apiRtr.HandleFunc("/api/v1/spire/agents/createjointoken", s.agentCreateJoinToken) + apiRtr.HandleFunc("/api/v1/spire/entries/list", s.entryList) + apiRtr.HandleFunc("/api/v1/spire/entries/create", s.entryCreate) + apiRtr.HandleFunc("/api/v1/spire/entries/delete", s.entryDelete) // Tornjak specific apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo) From 937011a23871175a74a32b5a020e0a0a1327c9e6 Mon Sep 17 00:00:00 2001 From: Ryan Ooi Date: Tue, 9 Jul 2024 15:10:06 -0400 Subject: [PATCH 10/39] update tornjak api to have versioning Signed-off-by: Ryan Ooi --- api/agent/server.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/agent/server.go b/api/agent/server.go index 15f74e97..341dfe60 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -599,15 +599,23 @@ func (s *Server) GetRouter() http.Handler { // Tornjak specific apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo) + apiRtr.HandleFunc("/api/tornjak/v1/serverinfo", s.tornjakGetServerInfo) // Agents Selectors apiRtr.HandleFunc("/api/tornjak/selectors/register", s.tornjakPluginDefine) + apiRtr.HandleFunc("/api/tornjak/v1/selectors/register", s.tornjakPluginDefine) apiRtr.HandleFunc("/api/tornjak/selectors/list", s.tornjakSelectorsList) + apiRtr.HandleFunc("/api/tornjak/v1/selectors/list", s.tornjakSelectorsList) apiRtr.HandleFunc("/api/tornjak/agents/list", s.tornjakAgentsList) + apiRtr.HandleFunc("/api/tornjak/v1/agents/list", s.tornjakAgentsList) // Clusters apiRtr.HandleFunc("/api/tornjak/clusters/list", s.clusterList) + apiRtr.HandleFunc("/api/tornjak/v1/clusters/list", s.clusterList) apiRtr.HandleFunc("/api/tornjak/clusters/create", s.clusterCreate) + apiRtr.HandleFunc("/api/tornjak/v1/clusters/create", s.clusterCreate) apiRtr.HandleFunc("/api/tornjak/clusters/edit", s.clusterEdit) + apiRtr.HandleFunc("/api/tornjak/v1/clusters/edit", s.clusterEdit) apiRtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete) + apiRtr.HandleFunc("/api/tornjak/v1/clusters/delete", s.clusterDelete) // Middleware apiRtr.Use(s.verificationMiddleware) From c011e228a438b0ae198a760ee82009ff62ffe41a Mon Sep 17 00:00:00 2001 From: Ryan Ooi Date: Thu, 11 Jul 2024 17:45:37 -0400 Subject: [PATCH 11/39] seperate old tornjak api from new v1 api Signed-off-by: Ryan Ooi --- api/agent/server.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index 341dfe60..f898dfb8 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -599,23 +599,29 @@ func (s *Server) GetRouter() http.Handler { // Tornjak specific apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo) - apiRtr.HandleFunc("/api/tornjak/v1/serverinfo", s.tornjakGetServerInfo) // Agents Selectors apiRtr.HandleFunc("/api/tornjak/selectors/register", s.tornjakPluginDefine) - apiRtr.HandleFunc("/api/tornjak/v1/selectors/register", s.tornjakPluginDefine) apiRtr.HandleFunc("/api/tornjak/selectors/list", s.tornjakSelectorsList) - apiRtr.HandleFunc("/api/tornjak/v1/selectors/list", s.tornjakSelectorsList) apiRtr.HandleFunc("/api/tornjak/agents/list", s.tornjakAgentsList) - apiRtr.HandleFunc("/api/tornjak/v1/agents/list", s.tornjakAgentsList) // Clusters apiRtr.HandleFunc("/api/tornjak/clusters/list", s.clusterList) - apiRtr.HandleFunc("/api/tornjak/v1/clusters/list", s.clusterList) apiRtr.HandleFunc("/api/tornjak/clusters/create", s.clusterCreate) - apiRtr.HandleFunc("/api/tornjak/v1/clusters/create", s.clusterCreate) apiRtr.HandleFunc("/api/tornjak/clusters/edit", s.clusterEdit) - apiRtr.HandleFunc("/api/tornjak/v1/clusters/edit", s.clusterEdit) apiRtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete) - apiRtr.HandleFunc("/api/tornjak/v1/clusters/delete", s.clusterDelete) + + // Tornjak specific + apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo) + // Agents Selectors + apiRtr.HandleFunc("/api/v1/tornjak/selectors/register", s.tornjakPluginDefine) + apiRtr.HandleFunc("/api/v1/tornjak/selectors/list", s.tornjakSelectorsList) + apiRtr.HandleFunc("/api/v1/tornjak/agents/list", s.tornjakAgentsList) + // Clusters + apiRtr.HandleFunc("/api/v1/tornjak/clusters/list", s.clusterList) + apiRtr.HandleFunc("/api/v1/tornjak/clusters/create", s.clusterCreate) + apiRtr.HandleFunc("/api/v1/tornjak/clusters/edit", s.clusterEdit) + apiRtr.HandleFunc("/api/v1/tornjak/clusters/delete", s.clusterDelete) + + // Middleware apiRtr.Use(s.verificationMiddleware) From 6b95461f042906917bd6ffbe5b6cf63432037300 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:54:36 +0000 Subject: [PATCH 12/39] Bump actions/setup-node from 4.0.2 to 4.0.3 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 113d57ba..cecefca3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: check-latest: true cache: true - - uses: actions/setup-node@v4.0.2 + - uses: actions/setup-node@v4.0.3 with: node-version: '18' From a069b1fcdec3156aaecdbd3f81f02361f9c5f93d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 13:49:59 -0400 Subject: [PATCH 13/39] Bump actions/setup-go from 5.0.1 to 5.0.2 (#462) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5.0.1...v5.0.2) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cecefca3..543eafcd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Install Golang - uses: actions/setup-go@v5.0.1 + uses: actions/setup-go@v5.0.2 with: go-version-file: go.mod check-latest: true From 5ea0362745824ad2111279edbd98a0b9bf439ced Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 09:54:00 -0400 Subject: [PATCH 14/39] Bump docker/login-action from 3.2.0 to 3.3.0 (#464) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3.2.0...v3.3.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/master-build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master-build.yaml b/.github/workflows/master-build.yaml index bf8deb35..7305bd8a 100644 --- a/.github/workflows/master-build.yaml +++ b/.github/workflows/master-build.yaml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4.1.7 - name: Log in to GHCR.io - uses: docker/login-action@v3.2.0 + uses: docker/login-action@v3.3.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -62,7 +62,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v4.1.7 - name: Log in to GHCR.io - uses: docker/login-action@v3.2.0 + uses: docker/login-action@v3.3.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From dc6c68152d0d9b6f2128def3b1408a51d5704f21 Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:29:23 -0400 Subject: [PATCH 15/39] Remove verb from API paths in favor of methods for v1 apis (#465) * Removed verb from API paths Signed-off-by: Xuhang Cao * Addressed pull request comments Signed-off-by: Xuhang Cao * Fixed 1 last nit Signed-off-by: Xuhang Cao --------- Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- api/agent/server.go | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index f898dfb8..ef0d743e 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -409,7 +409,7 @@ func (s *Server) entryDelete(w http.ResponseWriter, r *http.Request) { func cors(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, access-control-allow-origin, access-control-allow-headers, access-control-allow-credentials, Authorization, access-control-allow-methods") w.Header().Set("Access-Control-Expose-Headers", "*, Authorization") w.WriteHeader(http.StatusOK) @@ -418,7 +418,7 @@ func cors(w http.ResponseWriter, _ *http.Request) { func retError(w http.ResponseWriter, emsg string, status int) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, access-control-allow-origin, access-control-allow-headers, access-control-allow-credentials, Authorization, access-control-allow-methods") w.Header().Set("Access-Control-Expose-Headers", "*, Authorization") http.Error(w, emsg, status) @@ -586,17 +586,6 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/entry/create", s.entryCreate) apiRtr.HandleFunc("/api/entry/delete", s.entryDelete) - // Spire APIs with versioning - apiRtr.HandleFunc("/api/v1/spire/debugserver", s.debugServer) - apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck) - apiRtr.HandleFunc("/api/v1/spire/agents/list", s.agentList) - apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan) - apiRtr.HandleFunc("/api/v1/spire/agents/delete", s.agentDelete) - apiRtr.HandleFunc("/api/v1/spire/agents/createjointoken", s.agentCreateJoinToken) - apiRtr.HandleFunc("/api/v1/spire/entries/list", s.entryList) - apiRtr.HandleFunc("/api/v1/spire/entries/create", s.entryCreate) - apiRtr.HandleFunc("/api/v1/spire/entries/delete", s.entryDelete) - // Tornjak specific apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo) // Agents Selectors @@ -609,19 +598,28 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/tornjak/clusters/edit", s.clusterEdit) apiRtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete) + // Spire APIs with versioning + apiRtr.HandleFunc("/api/v1/spire/serverinfo", s.debugServer).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/agents", s.agentList).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan).Methods("POST") + apiRtr.HandleFunc("/api/v1/spire/agents", s.agentDelete).Methods("DELETE") + apiRtr.HandleFunc("/api/v1/spire/agents/jointoken", s.agentCreateJoinToken).Methods("POST") + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryList).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryCreate).Methods("POST") + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryDelete).Methods("DELETE") + // Tornjak specific - apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo) + apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo).Methods("GET") // Agents Selectors - apiRtr.HandleFunc("/api/v1/tornjak/selectors/register", s.tornjakPluginDefine) - apiRtr.HandleFunc("/api/v1/tornjak/selectors/list", s.tornjakSelectorsList) - apiRtr.HandleFunc("/api/v1/tornjak/agents/list", s.tornjakAgentsList) + apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakPluginDefine).Methods("POST") + apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakSelectorsList).Methods("GET") + apiRtr.HandleFunc("/api/v1/tornjak/agents", s.tornjakAgentsList).Methods("GET") // Clusters - apiRtr.HandleFunc("/api/v1/tornjak/clusters/list", s.clusterList) - apiRtr.HandleFunc("/api/v1/tornjak/clusters/create", s.clusterCreate) - apiRtr.HandleFunc("/api/v1/tornjak/clusters/edit", s.clusterEdit) - apiRtr.HandleFunc("/api/v1/tornjak/clusters/delete", s.clusterDelete) - - + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterList).Methods("GET") + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterCreate).Methods("POST") + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterEdit).Methods("PATCH") + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterDelete).Methods("DELETE") // Middleware apiRtr.Use(s.verificationMiddleware) From 1fe882c8eccde068ac01fea75b7700c2db295076 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:33:05 +0000 Subject: [PATCH 16/39] Bump golangci/golangci-lint-action from 6.0.1 to 6.1.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.0.1...v6.1.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 543eafcd..fc6cde8c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,7 +39,7 @@ jobs: run: go mod download - name: golangci-lint - uses: golangci/golangci-lint-action@v6.0.1 + uses: golangci/golangci-lint-action@v6.1.0 with: version: v1.57.2 args: --timeout 7m From a5970e40067dfeefffc5a61d4c66d4903a767dd6 Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Fri, 2 Aug 2024 11:19:11 -0400 Subject: [PATCH 17/39] Update usage documentation and clean up contributing (#469) Signed-off-by: Maia Iyer --- CONTRIBUTING.md | 21 ++++++++++++--------- USAGE.md | 35 +++++++++++------------------------ 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e331f2eb..3a8e110d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,10 +9,12 @@ ## Contributor Guidelines and Governance -Please see [CONTRIBUTING](https://github.com/spiffe/spiffe/blob/main/CONTRIBUTING.md) and [GOVERNANCE](https://github.com/spiffe/spiffe/blob/main/GOVERNANCE.md) from the SPIFFE project. +Please see [CONTRIBUTING](https://github.com/spiffe/spiffe/blob/main/CONTRIBUTING.md) and [GOVERNANCE](https://github.com/spiffe/spiffe/blob/main/GOVERNANCE.md) from the SPIFFE project for community guidelines. > [!IMPORTANT] -> Before opening a new issue or PR, search for any existing issues [here](https://github.com/spiffe/tornjak/issues) to avoid duplication. +> Before opening a new issue, search for any existing issues [here](https://github.com/spiffe/tornjak/issues) to avoid duplication. + +If you're new to this project, we recommend you join us on [Slack](https://spiffe.slack.com/archives/C024JTTK58T) for discussion of potential new features. ## Pre-built images @@ -29,27 +31,28 @@ In order to build, we require the following installations: ## Building Executables and Images -Building Tornjak manually can be done with the Makefile. Notable make targets follow: +Building Tornjak manually can be done with the Makefile. Below is a list of local executable builds: - `make bin/tornjak-backend`: makes the Go executable of the Tornjak backend - `make bin/tornjak-manager`: makes the Go executable of the Tornjak manager - `make frontend-local-build`: makes the optimized ReactJS app locally for the Tornjak frontend. Uses environment variable configuration as in tornjak-frontend/.env + +And below is a list of container image builds: - `make image-tornjak-backend`: containerizes Go executable of the Tornjak backend - `make image-tornjak-manager`:containerizes Go executable of the Tornjak manager - `make image-tornjak-frontend`: containerizes React JS app for the Tornjak frontend -- `make image-tornjak`: containerizes Tornjak backend with Tornjak frontend For usage instructions of the containers, please see our [USAGE document](./USAGE.md) to get started. ## Development -We welcome all development attempt and contributions from the community. The easiest place to start is by reviewing our code architecture diagrams available in our [api documentation](./docs/tornjak-ui-api-documentation.md#11-overview). +We welcome all development attempts and contributions from the community. The easiest place to start is by reviewing our code architecture diagrams available in our [api documentation](./docs/tornjak-ui-api-documentation.md#11-overview). ## Opening a pull request -1. Fork/ clone the tornjak repo +1. Fork the tornjak repo 2. Ensure your branch is based on the latest commit in `dev` -3. Commit changes to your fork -4. Make sure your commit messages contain a `Signed-off-by: ` line (see `git-commit --signoff`) to certify the [DCO](/DCO) +3. Commit changes to your fork. Make sure your commit messages contain a `Signed-off-by: ` line (see `git-commit --signoff`) to certify the [DCO](/DCO) +4. Test your PR locally and ensure all tests in Github actions pass 5. Open a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) against the upstream `dev` branch @@ -57,7 +60,7 @@ We welcome all development attempt and contributions from the community. The eas > Please make sure you open all PRs against the `dev` branch > [!IMPORTANT] -> For any new feature design, or feature level changes, please create an issue 1st, then submit a PR with design details before code implementation. +> For any new feature design, or feature level changes, please create an issue first, then submit a PR with design details before code implementation. ## After your pull request is submitted diff --git a/USAGE.md b/USAGE.md index 96efdcfa..af317562 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,14 +1,14 @@ # Usage -We publish four container images currently: +We publish and support three container images currently: - [Tornjak Backend](https://github.com/spiffe/tornjak/pkgs/container/tornjak-backend): This image can be deployed as a sidecar with any SPIRE server. - [Tornjak Manager](https://github.com/spiffe/tornjak/pkgs/container/tornjak-manager): A container that runs this image exposes a port to register multiple Tornjak backends and forward typical commands to multiple Tornjak backends from one API. - [Tornjak Frontend](https://github.com/spiffe/tornjak/pkgs/container/tornjak-frontend): This image is typically deployed after the Tornjak Backend or Manager are deployed, as it requires a URL to connect directly to the Tornjak backend API. -- [Tornjak](https://github.com/spiffe/tornjak/pkgs/container/tornjak): This image containing both Tornjak Backend and Frontend components can deployed as a sidecar alongside a SPIRE Server container -NOTE: Previously, we had images placing the Tornjak backend and SPIRE server in the same container, but these are currently deprecated. The above is a comprehensive list of images +> [!NOTE] +> Previously, we had images placing the Tornjak backend and SPIRE server in the same container, but these are currently deprecated. The above is a comprehensive list of supported images -Pre-built images can be found at the above links. To decide which tag to use, typically choose a release from [this page](https://github.com/spiffe/tornjak/releases) and choose the corresponding tag. For example, if you are interested in release `tornjak-1.0.2`, then choose image tag `v1.0.2`. +Pre-built images can be found at the above links. To decide which tag to use, typically choose a release from [this page](https://github.com/spiffe/tornjak/releases) and choose the corresponding tag. For example, if you are interested in release `v1.7.0`, then choose image tag `v1.7.0`. ### Compatibility Table @@ -17,11 +17,11 @@ Please see below for compatibility charts of SPIRE server versions with Tornjak: | Tornjak version | SPIRE Server version | | :--------------------- | :------------------- | | v1.1.x, v1.2.x, v1.3.x | v1.1.x, v1.2.x, v1.3.x, v1.4.x | -| v1.4.x, v1.5.x, v1.6.x | v1.5.x, v1.6.x, v1.7.x, v1.8.x, v1.9.x| +| v1.4.x, v1.5.x, v1.6.x, v1.7.x | v1.5.x, v1.6.x, v1.7.x, v1.8.x, v1.9.x| -## Tornjak Backend +## [Tornjak Backend](https://github.com/spiffe/tornjak/pkgs/container/tornjak-backend) -This is meant to be deployed where it can access a SPIRE server. To run, the container has three arguments: +The backend is designed to be deployed where it can access a SPIRE server. To run, the container has three arguments: | Flag | Description | Default | Arguments | Required | |:-----------------------|:------------------------------------------------------------|:--------|:----------|:---------| @@ -49,7 +49,9 @@ This creates a service listening on container port 50000, forwarded to localhost ## Tornjak Frontend -The frontend is meant to connect to either the Tornjak backend or the Tornjak manager. To run the container, we must set some environment variables: +The Tornjak frontend container exposes a browser application and must be able to connect to either the Tornjak backend or the Tornjak manager. + +The container requires certain environment variables be set. Below is a comprehensive list of all environment variables: | Variable | Description | Default | Example Argument | Required | |:----------------------------|-------------|--|--|--| @@ -64,26 +66,11 @@ The frontend is meant to connect to either the Tornjak backend or the Tornjak ma | `REACT_APP_SPIRE_HEALTH_CHECK_ENABLE` | Enable SPIRE health check component | `false` | `true` | false | ``` -docker run -p 3000:8080 -e REACT_APP_API_SERVER_URI='http://localhost:50000' -e REACT_APP_TORNJAK_MANAGER=true -e PORT_FE-8080 -e REACT_APP_SPIRE_HEALTH_CHECK=true ghcr.io/spiffe/tornjak-frontend:latest +docker run -p 3000:8080 -e REACT_APP_API_SERVER_URI='http://localhost:50000' -e REACT_APP_TORNJAK_MANAGER=true -e PORT_FE=8080 -e REACT_APP_SPIRE_HEALTH_CHECK=true ghcr.io/spiffe/tornjak-frontend:latest ``` The above command is an example of how to run the frontend. This creates a UI available at http://localhost:3000 forwarded from container port `8080`. It is listening to a Tornjak manager component available at http://localhost:50000, and knows to run in manager mode with the `REACT_APP_TORNJAK_MANAGER` flag. The last environment variables namely, `REACT_APP_SPIRE_HEALTH_CHECK_ENABLE` is used to enable the SPIRE health check component. -## Tornjak - -This container may be used as an alternative to having a frontend and backend container separately. The backend is configured exactly as the [Tornjak backend] with container arguments, and the frontend is configured exactly as the [Tornjak frontend] with container environment variables. - -An example command: - -``` -docker run -p 10000:10000 -p 3000:8080 -e REACT_APP_API_SERVER_URI='http://localhost:10000' -e PORT_FE-8080 -e PORT_BE-10000 ghcr.io/spiffe/tornjak:latest --spire-config --tornjak-config -``` - -The above command creates a UI available at `http://localhost:3000` forwarded from container port `8080`. It is listening to the Tornjak backend at `http://localhost:10000`, as given by the `REACT_APP_API_SERVER_URI` value. At the same time, the container is exposing port `10000` for the backend, which reads the SPIRE config and Tornjak config at `` and `` respectively. - -NOTE: The value of `REACT_APP_API_SERVER_URI` must be a URI that is separately available to any browser that accesses the frontend. Therefore, in production environments, it is necessary that backend service endpoint be public. - - ## Further steps It is recommended to try a full deployment of the Tornjak frontend, backend, and SPIRE Server in minikube. Please see our [tutorial document](docs/quickstart/README.md) for step-by-step instructions. From 1603db1f56cd37d072935633d73a88f4e9be1f07 Mon Sep 17 00:00:00 2001 From: Vanessa de Jesus Silva Sarmento <67933060+vjesu5@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:31:01 -0300 Subject: [PATCH 18/39] Rename directory in Tornjak Backend container (#463) * refactor: updates the tornjak directory for backend dockerfiles Signed-off-by: Vanessa de Jesus Silva * refactor: updates the tornjak directory references for backend runner script Signed-off-by: Vanessa de Jesus Silva * refactor: updates the tornjak directory references in examples/ dir Signed-off-by: Vanessa de Jesus Silva --------- Signed-off-by: Vanessa de Jesus Silva Sarmento <67933060+vjesu5@users.noreply.github.com> --- Dockerfile.backend-container | 6 +++--- Dockerfile.backend-container.ubi | 6 +++--- examples/tls_mtls/README.md | 6 +++--- examples/tls_mtls/server-statefulset-mtls.yaml | 4 ++-- examples/tls_mtls/server-statefulset-tls.yaml | 2 +- scripts/run_backend.sh | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Dockerfile.backend-container b/Dockerfile.backend-container index be552152..e76fac0c 100644 --- a/Dockerfile.backend-container +++ b/Dockerfile.backend-container @@ -18,10 +18,10 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-alpine-linux-musl; fi && \ go build --tags 'sqlite_json' -mod=vendor -ldflags '-s -w -linkmode external -extldflags "-static"' -o bin/tornjak-backend ./cmd/agent/main.go FROM alpine AS runtime -RUN mkdir -p /opt/spire +RUN mkdir -p /opt/tornjak -WORKDIR /opt/spire -ENTRYPOINT ["/opt/spire/run_backend.sh"] +WORKDIR /opt/tornjak +ENTRYPOINT ["/opt/tornjak/run_backend.sh"] # Add init COPY scripts/run_backend.sh run_backend.sh diff --git a/Dockerfile.backend-container.ubi b/Dockerfile.backend-container.ubi index 1901bcec..eeb4102a 100644 --- a/Dockerfile.backend-container.ubi +++ b/Dockerfile.backend-container.ubi @@ -18,10 +18,10 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-alpine-linux-musl; fi && \ go build --tags 'sqlite_json' -mod=vendor -ldflags '-s -w -linkmode external -extldflags "-static"' -o bin/tornjak-backend ./cmd/agent/main.go FROM registry.access.redhat.com/ubi8-micro:latest AS runtime -RUN mkdir -p /opt/spire +RUN mkdir -p /opt/tornjak -WORKDIR /opt/spire -ENTRYPOINT ["/opt/spire/run_backend.sh"] +WORKDIR /opt/tornjak +ENTRYPOINT ["/opt/tornjak/run_backend.sh"] # Add init COPY scripts/run_backend.sh run_backend.sh diff --git a/examples/tls_mtls/README.md b/examples/tls_mtls/README.md index bbcedc0d..a647e37a 100644 --- a/examples/tls_mtls/README.md +++ b/examples/tls_mtls/README.md @@ -86,7 +86,7 @@ cat server-statefulset-tls.yaml volumeMounts: ... - name: tls-volume - mountPath: /opt/spire/server + mountPath: /opt/tornjak/server ... volumes: ... @@ -123,9 +123,9 @@ Then we mount the secret to the Tornjak container via volume mount, as in the pr volumeMounts: ... - name: tls-volume - mountPath: /opt/spire/server + mountPath: /opt/tornjak/server - name: client-cas - mountPath: /opt/spire/clients + mountPath: /opt/tornjak/clients ... volumes: ... diff --git a/examples/tls_mtls/server-statefulset-mtls.yaml b/examples/tls_mtls/server-statefulset-mtls.yaml index 5784e012..981fa4e4 100644 --- a/examples/tls_mtls/server-statefulset-mtls.yaml +++ b/examples/tls_mtls/server-statefulset-mtls.yaml @@ -71,9 +71,9 @@ spec: - name: socket mountPath: /tmp/spire-server/private - name: tls-volume # 👈 TLS SECRET VOLUME MOUNT - mountPath: /opt/spire/server # 👈 TLS SECRET VOLUME MOUNT + mountPath: /opt/tornjak/server # 👈 TLS SECRET VOLUME MOUNT - name: client-ca # 👈 mTLS CA SECRET VOLUME MOUNT - mountPath: /opt/spire/clients # 👈 mTLS CA SECRET VOLUME MOUNT + mountPath: /opt/tornjak/clients # 👈 mTLS CA SECRET VOLUME MOUNT volumes: - name: spire-config configMap: diff --git a/examples/tls_mtls/server-statefulset-tls.yaml b/examples/tls_mtls/server-statefulset-tls.yaml index b03b0b39..3a9120c9 100644 --- a/examples/tls_mtls/server-statefulset-tls.yaml +++ b/examples/tls_mtls/server-statefulset-tls.yaml @@ -71,7 +71,7 @@ spec: - name: socket mountPath: /tmp/spire-server/private - name: secret-volume # 👈 TLS SECRET VOLUME MOUNT - mountPath: /opt/spire/server # 👈 TLS SECRET VOLUME MOUNT + mountPath: /opt/tornjak/server # 👈 TLS SECRET VOLUME MOUNT volumes: - name: spire-config configMap: diff --git a/scripts/run_backend.sh b/scripts/run_backend.sh index 9fc48fc3..e32d4e6b 100755 --- a/scripts/run_backend.sh +++ b/scripts/run_backend.sh @@ -2,7 +2,7 @@ echo "${@}" # run serverinfo to print SPIRE config if given and Tornjak config -/opt/spire/tornjak-backend ${@} serverinfo +/opt/tornjak/tornjak-backend ${@} serverinfo # run Tornjak server -/opt/spire/tornjak-backend ${@} http +/opt/tornjak/tornjak-backend ${@} http From d2c73fb09d1f86731d1cf1d6bfe9580f9de2c4e8 Mon Sep 17 00:00:00 2001 From: Mohammed Abdi Date: Mon, 12 Aug 2024 11:49:29 -0400 Subject: [PATCH 19/39] Added spire server unique identifier (#472) * add trust domain as spire server unique identifier Signed-off-by: MohammedAbdi * update component Signed-off-by: MohammedAbdi * add spire server path Signed-off-by: MohammedAbdi * update server id Signed-off-by: MohammedAbdi * updat trust domain view to be bolder for identification Signed-off-by: MohammedAbdi --------- Signed-off-by: Mohammed Abdi --- frontend/src/components/navbar.tsx | 54 ++++++++++++++++++++++++++++-- frontend/src/components/style.css | 10 +++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index 080f7b6f..7e9aca4a 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -8,17 +8,26 @@ import tornjak_logo from "res/tornjak_logo.png"; import TornjakHelper from 'components/tornjak-helper'; import KeycloakService from "auth/KeycloakAuth"; import { RootState } from 'redux/reducers'; +import TornjakApi from './tornjak-api-helpers'; import { clickedDashboardTableFunc, isAuthenticatedUpdateFunc, accessTokenUpdateFunc, UserRolesUpdateFunc, + serverInfoUpdateFunc, + tornjakServerInfoUpdateFunc, + spireDebugServerInfoUpdateFunc, + tornjakMessageFunc } from 'redux/actions'; +import { Tag } from 'carbon-components-react'; import { - AccessToken + AccessToken, + ServerInfo, + DebugServerInfo, + TornjakServerInfo } from './types'; import HeaderToolBar from './navbar-header-toolbar'; -import {env} from '../env'; +import { env } from '../env'; const Auth_Server_Uri = env.REACT_APP_AUTH_SERVER_URI; @@ -39,15 +48,31 @@ type NavigationBarProp = { clickedDashboardTableFunc: (globalClickedDashboardTable: string) => void, // the clicked dashboard table globalClickedDashboardTable: string, + // the server trust domain and nodeAttestorPlugin as a ServerInfoType + globalServerInfo: ServerInfo, + // tornjak server debug info of the selected server + globalDebugServerInfo: DebugServerInfo, + // tornjak server info of the selected server + globalTornjakServerInfo: TornjakServerInfo, + // dispatches a payload for the server trust domain and nodeAttestorPlugin as a ServerInfoType and has a return type of void + serverInfoUpdateFunc: (globalServerInfo: ServerInfo) => void, + // dispatches a payload for the tornjak server info of the selected server and has a return type of void + tornjakServerInfoUpdateFunc: (globalTornjakServerInfo: TornjakServerInfo) => void, + // dispatches a payload for the debug server info of the selected server and has a return type of void + spireDebugServerInfoUpdateFunc: (globalDebugServerInfo: DebugServerInfo) => void, + // dispatches a payload for an Error Message/ Success Message of an executed function as a string and has a return type of void + tornjakMessageFunc: (globalErrorMessage: string) => void, } type NavigationBarState = {} class NavigationBar extends Component { TornjakHelper: TornjakHelper; + TornjakApi: TornjakApi; constructor(props: NavigationBarProp) { super(props); this.TornjakHelper = new TornjakHelper({}); + this.TornjakApi = new TornjakApi(props); this.state = {}; } @@ -64,6 +89,9 @@ class NavigationBar extends Component { } } } + this.TornjakApi.populateLocalTornjakServerInfo(this.props.tornjakServerInfoUpdateFunc, this.props.tornjakMessageFunc); + this.TornjakApi.populateLocalTornjakDebugServerInfo(this.props.spireDebugServerInfoUpdateFunc, this.props.tornjakMessageFunc); + this.TornjakApi.populateServerInfo(this.props.globalTornjakServerInfo, this.props.serverInfoUpdateFunc); } render() { @@ -133,6 +161,14 @@ class NavigationBar extends Component { Tornjak + {/* Temporarily using trust domain as server unique identifier */} +
+ + Server ID: + {this.props.globalServerInfo.trustDomain} + {this.props.globalDebugServerInfo.svid_chain[0].id.path} + +
); } @@ -149,11 +185,23 @@ const mapStateToProps = (state: RootState) => ({ globalIsAuthenticated: state.auth.globalIsAuthenticated, globalAccessToken: state.auth.globalAccessToken, globalUserRoles: state.auth.globalUserRoles, + globalServerInfo: state.servers.globalServerInfo, + globalDebugServerInfo: state.servers.globalDebugServerInfo, + globalTornjakServerInfo: state.servers.globalTornjakServerInfo, }) export default connect( mapStateToProps, - { clickedDashboardTableFunc, isAuthenticatedUpdateFunc, accessTokenUpdateFunc, UserRolesUpdateFunc } + { + clickedDashboardTableFunc, + isAuthenticatedUpdateFunc, + accessTokenUpdateFunc, + UserRolesUpdateFunc, + serverInfoUpdateFunc, + tornjakServerInfoUpdateFunc, + spireDebugServerInfoUpdateFunc, + tornjakMessageFunc + } )(NavigationBar) export { NavigationBar } \ No newline at end of file diff --git a/frontend/src/components/style.css b/frontend/src/components/style.css index 32064ed2..c996de5b 100644 --- a/frontend/src/components/style.css +++ b/frontend/src/components/style.css @@ -425,4 +425,12 @@ .delete-cluster-button { width: 180px; float: right; -} \ No newline at end of file +} +.spire-server-unique-identifier { + margin-top: 20px; + margin-right: 20px; + z-index: 1000; /* High z-index to ensure it's on top */ + position: relative; + display: inline; + float: right; /* Float to the right */ +} From 8e46b3afe3b585cd96a8d9ddaeca54e80affab92 Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Tue, 13 Aug 2024 10:22:55 -0400 Subject: [PATCH 20/39] Add SPIRE Bundle APIs (#478) * Add bundle functions Signed-off-by: Maia Iyer * add v1 apis Signed-off-by: Maia Iyer * format nit Signed-off-by: Maia Iyer * Nit uri fix Signed-off-by: Maia Iyer * gofmtted code Signed-off-by: Maia Iyer --------- Signed-off-by: Maia Iyer --- api/agent/api.go | 107 ++++++++++++++++++++++ api/agent/server.go | 216 ++++++++++++++++++++++++++++++++++++++++++-- api/agent/types.go | 16 ++-- 3 files changed, 325 insertions(+), 14 deletions(-) diff --git a/api/agent/api.go b/api/agent/api.go index 251e5bf9..0a6f9287 100644 --- a/api/agent/api.go +++ b/api/agent/api.go @@ -8,6 +8,7 @@ import ( "google.golang.org/grpc/credentials/insecure" agent "github.com/spiffe/spire-api-sdk/proto/spire/api/server/agent/v1" + bundle "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" debugServer "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" entry "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1" types "github.com/spiffe/spire-api-sdk/proto/spire/api/types" @@ -215,6 +216,112 @@ func (s *Server) GetTornjakServerInfo(inp GetTornjakServerInfoRequest) (*GetTorn return (*GetTornjakServerInfoResponse)(&s.SpireServerInfo), nil } +// Bundle APIs +type GetBundleRequest bundle.GetBundleRequest +type GetBundleResponse types.Bundle + +func (s *Server) GetBundle(inp GetBundleRequest) (*GetBundleResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := bundle.GetBundleRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := bundle.NewBundleClient(conn) + + bundle, err := client.GetBundle(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*GetBundleResponse)(bundle), nil +} + +type ListFederatedBundlesRequest bundle.ListFederatedBundlesRequest +type ListFederatedBundlesResponse bundle.ListFederatedBundlesResponse + +func (s *Server) ListFederatedBundles(inp ListFederatedBundlesRequest) (*ListFederatedBundlesResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := bundle.ListFederatedBundlesRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := bundle.NewBundleClient(conn) + + bundle, err := client.ListFederatedBundles(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*ListFederatedBundlesResponse)(bundle), nil +} + +type CreateFederatedBundleRequest bundle.BatchCreateFederatedBundleRequest +type CreateFederatedBundleResponse bundle.BatchCreateFederatedBundleResponse + +func (s *Server) CreateFederatedBundle(inp CreateFederatedBundleRequest) (*CreateFederatedBundleResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := bundle.BatchCreateFederatedBundleRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := bundle.NewBundleClient(conn) + + bundle, err := client.BatchCreateFederatedBundle(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*CreateFederatedBundleResponse)(bundle), nil +} + +type UpdateFederatedBundleRequest bundle.BatchUpdateFederatedBundleRequest +type UpdateFederatedBundleResponse bundle.BatchUpdateFederatedBundleResponse + +func (s *Server) UpdateFederatedBundle(inp UpdateFederatedBundleRequest) (*UpdateFederatedBundleResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := bundle.BatchUpdateFederatedBundleRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := bundle.NewBundleClient(conn) + + bundle, err := client.BatchUpdateFederatedBundle(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*UpdateFederatedBundleResponse)(bundle), nil +} + +type DeleteFederatedBundleRequest bundle.BatchDeleteFederatedBundleRequest +type DeleteFederatedBundleResponse bundle.BatchDeleteFederatedBundleResponse + +func (s *Server) DeleteFederatedBundle(inp DeleteFederatedBundleRequest) (*DeleteFederatedBundleResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := bundle.BatchDeleteFederatedBundleRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := bundle.NewBundleClient(conn) + + bundle, err := client.BatchDeleteFederatedBundle(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*DeleteFederatedBundleResponse)(bundle), nil +} + /* Agent diff --git a/api/agent/server.go b/api/agent/server.go index ef0d743e..30816616 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -1,6 +1,7 @@ package api import ( + "crypto/tls" "encoding/json" "fmt" "io" @@ -11,7 +12,6 @@ import ( "path/filepath" "strings" "time" - "crypto/tls" backoff "github.com/cenkalti/backoff/v4" "github.com/gorilla/mux" @@ -20,9 +20,9 @@ import ( "github.com/hashicorp/hcl/hcl/token" "github.com/pkg/errors" - agentdb "github.com/spiffe/tornjak/pkg/agent/db" "github.com/spiffe/tornjak/pkg/agent/authentication/authenticator" "github.com/spiffe/tornjak/pkg/agent/authorization" + agentdb "github.com/spiffe/tornjak/pkg/agent/db" ) type Server struct { @@ -36,9 +36,9 @@ type Server struct { TornjakConfig *TornjakConfig // Plugins - Db agentdb.AgentDB + Db agentdb.AgentDB Authenticator authenticator.Authenticator - Authorizer authorization.Authorizer + Authorizer authorization.Authorizer } // config type, as defined by SPIRE @@ -406,6 +406,207 @@ func (s *Server) entryDelete(w http.ResponseWriter, r *http.Request) { } } +// Bundle APIs +func (s *Server) bundleGet(w http.ResponseWriter, r *http.Request) { + var input GetBundleRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = GetBundleRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.GetBundle(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federatedBundleList(w http.ResponseWriter, r *http.Request) { + var input ListFederatedBundlesRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = ListFederatedBundlesRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.ListFederatedBundles(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federatedBundleCreate(w http.ResponseWriter, r *http.Request) { + var input CreateFederatedBundleRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = CreateFederatedBundleRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.CreateFederatedBundle(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federatedBundleUpdate(w http.ResponseWriter, r *http.Request) { + var input UpdateFederatedBundleRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = UpdateFederatedBundleRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.UpdateFederatedBundle(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federatedBundleDelete(w http.ResponseWriter, r *http.Request) { + var input DeleteFederatedBundleRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = DeleteFederatedBundleRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.DeleteFederatedBundle(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + func cors(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Header().Set("Access-Control-Allow-Origin", "*") @@ -558,7 +759,6 @@ func (s *Server) health(w http.ResponseWriter, r *http.Request) { } } - func (s *Server) GetRouter() http.Handler { rtr := mux.NewRouter() @@ -608,6 +808,11 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/v1/spire/entries", s.entryList).Methods("GET") apiRtr.HandleFunc("/api/v1/spire/entries", s.entryCreate).Methods("POST") apiRtr.HandleFunc("/api/v1/spire/entries", s.entryDelete).Methods("DELETE") + apiRtr.HandleFunc("/api/v1/spire/bundle", s.bundleGet).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleList).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleCreate).Methods("POST") + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleUpdate).Methods("PATCH") + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleDelete).Methods("DELETE") // Tornjak specific apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo).Methods("GET") @@ -681,7 +886,6 @@ func (s *Server) HandleRequests() { httpsConfig := serverConfig.HTTPSConfig var tlsConfig *tls.Config - if serverConfig.HTTPSConfig.ListenPort == 0 { // Fail because this is required field in this section err = fmt.Errorf("HTTPS Config error: no port configured. Starting insecure HTTP connection at %d...", serverConfig.HTTPConfig.ListenPort) diff --git a/api/agent/types.go b/api/agent/types.go index b5e39911..2e4b14b5 100644 --- a/api/agent/types.go +++ b/api/agent/types.go @@ -27,8 +27,8 @@ type SpireServerConfig struct { } type SPIREConfig struct { - Server *SpireServerConfig `hcl:"server"` - Plugins ast.Node `hcl:"plugins"` + Server *SpireServerConfig `hcl:"server"` + Plugins ast.Node `hcl:"plugins"` } type TornjakConfig struct { @@ -49,9 +49,9 @@ type HTTPConfig struct { } type HTTPSConfig struct { - ListenPort int `hcl:"port"` - Cert string `hcl:"cert"` - Key string `hcl:"key"` + ListenPort int `hcl:"port"` + Cert string `hcl:"cert"` + Key string `hcl:"key"` ClientCA string `hcl:"client_ca"` } @@ -119,12 +119,12 @@ type AuthRole struct { } type APIRoleMapping struct { - Name string `hcl:",key"` + Name string `hcl:",key"` AllowedRoles []string `hcl:"allowed_roles"` } type pluginAuthorizerRBAC struct { - Name string `hcl:"name"` - RoleList []*AuthRole `hcl:"role,block"` + Name string `hcl:"name"` + RoleList []*AuthRole `hcl:"role,block"` APIRoleMappings []*APIRoleMapping `hcl:"API,block"` } From 271e60d742b498a7f6d37dde813a5ee6bdf611fb Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:27:57 -0400 Subject: [PATCH 21/39] Print Authorization Plugin Statements (#479) * Print output Signed-off-by: Xuhang Cao * 8.20 Meeting Signed-off-by: Xuhang Cao * auth policy Signed-off-by: Xuhang Cao * auth plugin Signed-off-by: Xuhang Cao * Auth plugin Signed-off-by: Xuhang Cao --------- Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- api/agent/server.go | 7 + api/agent/types.go | 268 ++++++++++++++++++++------------------ docs/conf/agent/full.conf | 218 +++++++++++++++++-------------- 3 files changed, 265 insertions(+), 228 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index 30816616..e1602c20 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -1070,6 +1070,13 @@ func NewAuthorizer(authorizerPlugin *ast.ObjectItem) (authorization.Authorizer, } for _, api := range config.APIRoleMappings { apiMapping[api.Name] = api.AllowedRoles + fmt.Printf("API name: %s, Allowed Roles: %s \n", api.Name, api.AllowedRoles) + } + for _, apiV1 := range config.APIv1RoleMappings{ + arr := strings.Split(apiV1.Name, " ") + apiV1.Method = arr[0] + apiV1.Path = arr[1] + fmt.Printf("API V1 method: %s, API V1 path: %s, API V1 allowed roles: %s \n", apiV1.Method, apiV1.Path, apiV1.AllowedRoles) } authorizer, err := authorization.NewRBACAuthorizer(config.Name, roleList, apiMapping) diff --git a/api/agent/types.go b/api/agent/types.go index 2e4b14b5..8e996b00 100644 --- a/api/agent/types.go +++ b/api/agent/types.go @@ -1,130 +1,138 @@ -package api - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" - - "github.com/hashicorp/hcl/hcl/ast" -) - -// TornjakServerInfo provides insight into the configuration of the SPIRE server -// where the Tornjak Agent resides -type TornjakSpireServerInfo struct { - // Plugins is a map from plugin types to respective names of plugins configured - Plugins map[string][]string `json:"plugins"` - // TrustDomain specifies the trust domain of the SPIRE server configured with tornjak - TrustDomain string `json:"trustDomain"` - // Verbose config contains unstructure information on the config on the agent - VerboseConfig string `json:"verboseConfig"` -} - -// pared down version of full Server Config type spire/cmd/spire-server/cli/run -// we curently need only extract the trust domain -type SpireServerConfig struct { - TrustDomain string `hcl:"trust_domain"` -} - -type SPIREConfig struct { - Server *SpireServerConfig `hcl:"server"` - Plugins ast.Node `hcl:"plugins"` -} - -type TornjakConfig struct { - Server *serverConfig `hcl:"server"` - Plugins *ast.Node `hcl:"plugins"` -} - -/* Server configuration*/ - -type serverConfig struct { - SPIRESocket string `hcl:"spire_socket_path"` - HTTPConfig *HTTPConfig `hcl:"http"` - HTTPSConfig *HTTPSConfig `hcl:"https"` -} - -type HTTPConfig struct { - ListenPort int `hcl:"port"` -} - -type HTTPSConfig struct { - ListenPort int `hcl:"port"` - Cert string `hcl:"cert"` - Key string `hcl:"key"` - ClientCA string `hcl:"client_ca"` -} - -func (h HTTPSConfig) Parse() (*tls.Config, error) { - serverCertPath := h.Cert - serverKeyPath := h.Key - clientCAPath := h.ClientCA - - mtls := (clientCAPath != "") - - if _, err := os.Stat(serverCertPath); os.IsNotExist(err) { - return nil, fmt.Errorf("server cert path '%s': %w", serverCertPath, err) - } - if _, err := os.Stat(serverKeyPath); os.IsNotExist(err) { - return nil, fmt.Errorf("server key path '%s': %w", serverKeyPath, err) - } - - // Create a CA certificate pool and add cert.pem to it - serverCert, err := os.ReadFile(serverCertPath) - if err != nil { - return nil, fmt.Errorf("server ca pool error: %w", err) - } - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(serverCert) - - if mtls { - // add mTLS CA path to cert pool as well - if _, err := os.Stat(clientCAPath); os.IsNotExist(err) { - return nil, fmt.Errorf("server file does not exist %s", clientCAPath) - } - clientCA, err := os.ReadFile(clientCAPath) - if err != nil { - return nil, fmt.Errorf("server: could not read file %s: %w", clientCAPath, err) - } - caCertPool.AppendCertsFromPEM(clientCA) - } - - // Create the TLS Config with the CA pool and enable Client certificate validation - tlsConfig := &tls.Config{ - ClientCAs: caCertPool, - } - - if mtls { - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - } - //tlsConfig.BuildNameToCertificate() - - return tlsConfig, nil -} - -/* Plugin types */ -type pluginDataStoreSQL struct { - Drivername string `hcl:"drivername"` - Filename string `hcl:"filename"` -} - -type pluginAuthenticatorKeycloak struct { - IssuerURL string `hcl:"issuer"` - Audience string `hcl:"audience"` -} - -type AuthRole struct { - Name string `hcl:",key"` - Desc string `hcl:"desc"` -} - -type APIRoleMapping struct { - Name string `hcl:",key"` - AllowedRoles []string `hcl:"allowed_roles"` -} - -type pluginAuthorizerRBAC struct { - Name string `hcl:"name"` - RoleList []*AuthRole `hcl:"role,block"` - APIRoleMappings []*APIRoleMapping `hcl:"API,block"` -} +package api + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + + "github.com/hashicorp/hcl/hcl/ast" +) + +// TornjakServerInfo provides insight into the configuration of the SPIRE server +// where the Tornjak Agent resides +type TornjakSpireServerInfo struct { + // Plugins is a map from plugin types to respective names of plugins configured + Plugins map[string][]string `json:"plugins"` + // TrustDomain specifies the trust domain of the SPIRE server configured with tornjak + TrustDomain string `json:"trustDomain"` + // Verbose config contains unstructure information on the config on the agent + VerboseConfig string `json:"verboseConfig"` +} + +// pared down version of full Server Config type spire/cmd/spire-server/cli/run +// we curently need only extract the trust domain +type SpireServerConfig struct { + TrustDomain string `hcl:"trust_domain"` +} + +type SPIREConfig struct { + Server *SpireServerConfig `hcl:"server"` + Plugins ast.Node `hcl:"plugins"` +} + +type TornjakConfig struct { + Server *serverConfig `hcl:"server"` + Plugins *ast.Node `hcl:"plugins"` +} + +/* Server configuration*/ + +type serverConfig struct { + SPIRESocket string `hcl:"spire_socket_path"` + HTTPConfig *HTTPConfig `hcl:"http"` + HTTPSConfig *HTTPSConfig `hcl:"https"` +} + +type HTTPConfig struct { + ListenPort int `hcl:"port"` +} + +type HTTPSConfig struct { + ListenPort int `hcl:"port"` + Cert string `hcl:"cert"` + Key string `hcl:"key"` + ClientCA string `hcl:"client_ca"` +} + +func (h HTTPSConfig) Parse() (*tls.Config, error) { + serverCertPath := h.Cert + serverKeyPath := h.Key + clientCAPath := h.ClientCA + + mtls := (clientCAPath != "") + + if _, err := os.Stat(serverCertPath); os.IsNotExist(err) { + return nil, fmt.Errorf("server cert path '%s': %w", serverCertPath, err) + } + if _, err := os.Stat(serverKeyPath); os.IsNotExist(err) { + return nil, fmt.Errorf("server key path '%s': %w", serverKeyPath, err) + } + + // Create a CA certificate pool and add cert.pem to it + serverCert, err := os.ReadFile(serverCertPath) + if err != nil { + return nil, fmt.Errorf("server ca pool error: %w", err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(serverCert) + + if mtls { + // add mTLS CA path to cert pool as well + if _, err := os.Stat(clientCAPath); os.IsNotExist(err) { + return nil, fmt.Errorf("server file does not exist %s", clientCAPath) + } + clientCA, err := os.ReadFile(clientCAPath) + if err != nil { + return nil, fmt.Errorf("server: could not read file %s: %w", clientCAPath, err) + } + caCertPool.AppendCertsFromPEM(clientCA) + } + + // Create the TLS Config with the CA pool and enable Client certificate validation + tlsConfig := &tls.Config{ + ClientCAs: caCertPool, + } + + if mtls { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + //tlsConfig.BuildNameToCertificate() + + return tlsConfig, nil +} + +/* Plugin types */ +type pluginDataStoreSQL struct { + Drivername string `hcl:"drivername"` + Filename string `hcl:"filename"` +} + +type pluginAuthenticatorKeycloak struct { + IssuerURL string `hcl:"issuer"` + Audience string `hcl:"audience"` +} + +type AuthRole struct { + Name string `hcl:",key"` + Desc string `hcl:"desc"` +} + +type APIRoleMapping struct { + Name string `hcl:",key"` + AllowedRoles []string `hcl:"allowed_roles"` +} + +type APIv1RoleMapping struct { + Name string `hcl:",key"` + Method string `hcl:"-"` + Path string `hcl:"-"` + AllowedRoles []string `hcl:"allowed_roles"` +} + +type pluginAuthorizerRBAC struct { + Name string `hcl:"name"` + RoleList []*AuthRole `hcl:"role,block"` + APIRoleMappings []*APIRoleMapping `hcl:"API,block"` + APIv1RoleMappings []*APIv1RoleMapping `hcl:"APIv1,block"` +} diff --git a/docs/conf/agent/full.conf b/docs/conf/agent/full.conf index e1814daa..e5f63134 100644 --- a/docs/conf/agent/full.conf +++ b/docs/conf/agent/full.conf @@ -1,98 +1,120 @@ -server { - # location of SPIRE socket - # here, set to default SPIRE socket path - spire_socket_path = "unix:///tmp/spire-server/private/api.sock" - - ### BEGIN SERVER CONNECTION CONFIGURATION ### - # Note: at least one of http, tls, and mtls must be configured - # The server can open multiple if multiple sections included - # The server only ends when all connections error - - # [required] configure HTTP connection to Tornjak server - http { - port = 10000 # container port for HTTP connection - } - - # [optional, recommended] configure HTTPS connection to Tornjak server - https { - port = 10443 # [required for HTTPS] container port for HTTPS connection - cert = "sample-keys/tls.pem" # [required for HTTPS] TLS cert - key = "sample-keys/key.pem" # [required for HTTPS] TLS key - client_ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port - } - - ### END SERVER CONNECTION CONFIGURATION ### -} - -plugins { - - ### BEGIN DATASTORE PLUGIN CONFIGURATION ### - - # Configure SQL local database for Tornjak - DataStore "sql" { - plugin_data { - drivername = "sqlite3" - filename = "/run/spire/data/tornjak.sqlite3" # location of database - } - } - - ### END DATASTORE PLUGIN CONFIGURATION - - ### BEGIN IAM PLUGIN CONFIGURATION ### - # Note: if no UserManagement configuration included, authentication treated as noop - - # This plugin will extract roles from `realm_access.roles` in the JWT and pass - # to the authorization layer as user roles. - Authenticator "Keycloak" { - plugin_data { - # issuer - Issuer URL for OIDC - # here is a sample for Keycloak running locally on Minikube - issuer = "http://host.docker.internal:8080/realms/tornjak" - # for cloud deployment it would be something like: - # issuer = "http:///realms/tornjak" - - # audience - expected value for aud claim in JWT - # if not included or set, there will be no audience check - # recommended to ensure JWT was meant for Tornjak Backend resource server - audience = "tornjak-backend" - } - } - - # This policy requires admin role for all write calls, viewer role for all read calls - # and authentication success for the "/" api - Authorizer "RBAC" { - plugin_data { - name = "Admin Viewer Policy" - role "admin" { desc = "admin person" } - role "viewer" { desc = "viewer person" } - # this special character role is reserved for allowing all authenticated persons - role "" { desc = "authenticated person" } - - # home tornjak backend api allowed with any successful authentication - API "/" { allowed_roles = [""] } - # allowed with successful authentication and either admin or viewer role - API "/api/healthcheck" { allowed_roles = ["admin", "viewer"] } - API "/api/debugserver" { allowed_roles = ["admin", "viewer"] } - API "/api/agent/list" { allowed_roles = ["admin", "viewer"] } - API "/api/entry/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/selectors/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/agents/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/clusters/list" { allowed_roles = ["admin", "viewer"] } - # allowed with successful authentication and admin role - API "/api/agent/ban" { allowed_roles = ["admin"] } - API "/api/agent/delete" { allowed_roles = ["admin"] } - API "/api/agent/createjointoken" { allowed_roles = ["admin"] } - API "/api/entry/create" { allowed_roles = ["admin"] } - API "/api/entry/delete" { allowed_roles = ["admin"] } - API "/api/tornjak/selectors/register" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } - } - } - - ### END IAM PLUGIN CONFIGURATION - - -} +server { + # location of SPIRE socket + # here, set to default SPIRE socket path + spire_socket_path = "unix:///tmp/spire-server/private/api.sock" + + ### BEGIN SERVER CONNECTION CONFIGURATION ### + # Note: at least one of http, tls, and mtls must be configured + # The server can open multiple if multiple sections included + # The server only ends when all connections error + + # [required] configure HTTP connection to Tornjak server + http { + port = 10000 # container port for HTTP connection + } + + # [optional, recommended] configure HTTPS connection to Tornjak server + https { + port = 10443 # [required for HTTPS] container port for HTTPS connection + cert = "sample-keys/tls.pem" # [required for HTTPS] TLS cert + key = "sample-keys/key.pem" # [required for HTTPS] TLS key + client_ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port + } + + ### END SERVER CONNECTION CONFIGURATION ### +} + +plugins { + + ### BEGIN DATASTORE PLUGIN CONFIGURATION ### + + # Configure SQL local database for Tornjak + DataStore "sql" { + plugin_data { + drivername = "sqlite3" + filename = "tornjak.sqlite3" # location of database + } + } + + ### END DATASTORE PLUGIN CONFIGURATION + + ### BEGIN IAM PLUGIN CONFIGURATION ### + # Note: if no UserManagement configuration included, authentication treated as noop + + # This plugin will extract roles from `realm_access.roles` in the JWT and pass + # to the authorization layer as user roles. + Authenticator "Keycloak" { + plugin_data { + # issuer - Issuer URL for OIDC + # here is a sample for Keycloak running locally on Minikube + issuer = "http://host.docker.internal:8080/realms/tornjak" + # for cloud deployment it would be something like: + # issuer = "http:///realms/tornjak" + + # audience - expected value for aud claim in JWT + # if not included or set, there will be no audience check + # recommended to ensure JWT was meant for Tornjak Backend resource server + audience = "tornjak-backend" + } + } + + # This policy requires admin role for all write calls, viewer role for all read calls + # and authentication success for the "/" api + Authorizer "RBAC" { + plugin_data { + name = "Admin Viewer Policy" + role "admin" { desc = "admin person" } + role "viewer" { desc = "viewer person" } + # this special character role is reserved for allowing all authenticated persons + role "" { desc = "authenticated person" } + + # home tornjak backend api allowed with any successful authentication + API "/" { allowed_roles = [""] } + # allowed with successful authentication and either admin or viewer role + API "/api/healthcheck" { allowed_roles = ["admin", "viewer"] } + API "/api/debugserver" { allowed_roles = ["admin", "viewer"] } + API "/api/agent/list" { allowed_roles = ["admin", "viewer"] } + API "/api/entry/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/selectors/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/agents/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/clusters/list" { allowed_roles = ["admin", "viewer"] } + # allowed with successful authentication and admin role + API "/api/agent/ban" { allowed_roles = ["admin"] } + API "/api/agent/delete" { allowed_roles = ["admin"] } + API "/api/agent/createjointoken" { allowed_roles = ["admin"] } + API "/api/entry/create" { allowed_roles = ["admin"] } + API "/api/entry/delete" { allowed_roles = ["admin"] } + API "/api/tornjak/selectors/register" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/spire/serverinfo" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/healthcheck" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/entries" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/agents" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/spire/agents/ban" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/spire/agents" { allowed_roles = ["admin"] } + APIv1 "POST /api/v1/spire/agents/jointoken" { allowed_roles = ["admin"] } + APIv1 "POST /api/v1/spire/entries" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/spire/entries" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/spire/bundle" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/federations/bundles" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + APIv1 "PATCH /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/tornjak/selectors" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/tornjak/selectors" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/tornjak/agents" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/tornjak/clusters" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + APIv1 "PATCH /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + } + } + + ### END IAM PLUGIN CONFIGURATION + + +} From ff584017a460db6e90a579f76e13ef1069d5120b Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:12:53 -0400 Subject: [PATCH 22/39] Updated API documentations (#481) * Updated API documentations Signed-off-by: Xuhang Cao * Fixing nits Signed-off-by: Xuhang Cao --------- Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- openapi.yaml | 778 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 778 insertions(+) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 00000000..f1be170e --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,778 @@ +openapi: 3.1.0 +info: + title: Tornjak + description: | + This API allows management of SPIRE server via the Tornjak server, + comprised of SPIRE server API calls and Tornjak-specific API calls. + version: 1.3.0 +paths: + /api/v1/spire/healthcheck: + get: + summary: Query SPIRE Healthcheck status + description: Retrieves SPIRE healthcheck status. SPIRE currently uses Google grpc_health_v1 package + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + status: + type: integer + minimum: 0 + maximum: 3 + description: | + Possible values for SPIRE server healthcheck + `0` means UNKNOWN + `1` means SERVING + `2` means NOT_SERVING + `3` means SERVICE UNKNOWN + /api/v1/spire/serverinfo: + get: + summary: Get general SPIRE server information, as defined in SPIRE api-sdk + description: Retrieves general SPIRE server information as defined in SPIRE api-sdk + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + svid_chain: + type: array + items: + type: object + properties: + id: + type: object + properties: + trust_domain: + type: string + examples: ["example.org"] + path: + type: string + examples: ["/spire/server"] + expires_at: + type: integer + examples: [111] + subject: + type: string + examples: ["O=SPIRE,C=US,2.5.4.45=#222"] + uptime: + type: integer + minimum: 0 + examples: [333] + description: | + Represents the number of seconds a SPIRE server has been running + federated_bundles_count: + type: integer + minimum: 0 + examples: [1] + description: | + Represents the number of federated bundles stored in SPIRE server + example: # This part adds a full example for manual display + { + "svid_chain": [ + { + "id": { + "trust_domain": "example.org", + "path": "/spire/server" + }, + "expires_at": 111, + "subject": "O=SPIRE,C=US,2.5.4.45=#222" + }, + { + "id": { + "trust_domain": "example.org", + "path": "/spire/server/other" + }, + "expires_at": 222, + "subject": "O=SPIFFE,C=US" + } + ], + "uptime": 333, + "federated_bundles_count": 1 + } + /api/v1/spire/agents: + get: + summary: Calls SPIRE server `spire-server agent list` command + description: Display attested nodes + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + agents: + type: array + items: + $ref: '#/components/schemas/agent' + example: # Example response + { + "agents": [ + { + "id": { + "trust_domain": "example.org", + "path": "/spire/agent" + }, + "attestation_type": "k8s_sat", + "x509svid_serial_number": "111", + "x509svid_expires_at": 222, + "selectors": [ + { + "type": "k8s_sat", + "value": "agent_ns:spire" + }, + { + "type": "k8s_sat", + "value": "agent_sa:spire-agent" + } + ] + } + ] + } + + delete: + summary: Calls SPIRE server `spire-server agent evict` command + description: Evict attested node given spiffeID so node is not able to re-attest + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/spiffe_id' + examples: + spiffe_id_example: + value: + id: + path: "/spire/agent/" + trust_domain: "example.org" + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: string + examples: ["SUCCESS"] + /api/v1/spire/agents/ban: + post: + summary: Calls SPIRE server `spire-server agent ban` command + description: Ban attested node given spiffeID so node is not able to re-attest + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/spiffe_id' + examples: + example_request: + value: + id: + path: "/spire/agent/" + trust_domain: "example.org" + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: string + examples: ["SUCCESS"] + /api/v1/spire/agents/jointoken: + post: + summary: Calls SPIRE server `spire-server token generate` + description: Generates one node join token and creates a registration entry for it. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + path: + type: string + examples: ["/sample/spiffe/id"] + token: + type: integer + examples: [1111] + trust_domain: + type: string + examples: ["example.org"] + ttl: + type: integer + examples: [500] + examples: + example1: + value: + path: "/sample/spiffe/id" + token: 1111 + trust_domain: "example.org" + ttl: 500 + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + value: + type: integer + examples: [1111] + expires_at: + type: integer + examples: [555] + examples: + example1: + value: + value: 1111 + expires_at: 555 + /api/v1/spire/entries: + get: + summary: Calls SPIRE server `spire-server entry show` + description: Displays configured registration entries + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/entry' + examples: + example_response: + summary: "A sample response showing entries" + value: + entries: + - id: "id1" + spiffe_id: + trust_domain: "example.org" + path: "/spire/agent/" + parent_id: + trust_domain: "example.org" + path: "/spire/agent/" + selectors: + - type: "k8s_sat" + value: "agent_ns:spire" + - type: "k8s_sat" + value: "agent_sa:spire-agent" + post: + summary: Calls SPIRE server `spire-server entry create` + description: Create registration entries + requestBody: + required: true + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/entry' + - type: object + properties: + admin: + type: string + dns_names: + type: string + downstream: + type: string + expires_at: + type: string + federates_with: + type: array + items: + type: string + examples: [] + examples: + example_request: + summary: "Example request for creating a registration entry" + value: + admin: "/spire/agent/" + dns_names: "/spire/agent/" + downstream: "/spire/agent/" + expires_at: "/spire/agent/" + federates_with: [] + parent_id: + trust_domain: "example.org" + path: "/spire/agent/" + selectors: + - type: "k8s_sat" + value: "agent_ns:spire" + - type: "k8s_sat" + value: "agent_sa:spire-agent" + spiffe_id: + trust_domain: "example.org" + path: "/spire/agent/" + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + properties: + status: + type: object + properties: + message: + type: string + examples: ["OK"] + entry: + $ref: '#/components/schemas/entry' + examples: + example_response: + summary: "Example response after creating a registration entry" + value: + results: + - status: + message: "OK" + entry: + spiffe_id: + trust_domain: "example.org" + path: "/spire/agent/" + parent_id: + trust_domain: "example.org" + path: "/spire/agent/" + selectors: + - type: "k8s_sat" + value: "agent_ns:spire" + - type: "k8s_sat" + value: "agent_sa:spire-agent" + delete: + summary: Calls SPIRE server `spire-server entry delete` command + description: Deletes a specified registration entry + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + ids: + type: array + items: + type: string + examples: + - summary: "Example request to delete a registration entry" + value: + ids: + - "111" + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + Results: + type: array + items: + type: object + properties: + status: + type: object + properties: + message: + type: string + examples: + - "OK" + id: + type: string + examples: + - "111" + examples: + example_response: + summary: "Example response after deleting a registration entry" + value: + Results: + - status: + message: "OK" + id: "111" + + /api/v1/tornjak/serverinfo: + get: + summary: Get general Tornjak server information. + description: Get general Tornjak server information. + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + DataStore: + type: array + items: + type: string + examples: ["sql"] + KeyManager: + type: array + items: + type: string + examples: ["disk"] + NodeAttestor: + type: array + items: + type: string + examples: ["k8s_sat"] + NodeResolver: + type: array + items: + type: string + examples: ["k8sbundle"] + trust_domain: + type: string + examples: ["example.org"] + verboseConfig: + type: string + examples: ["Plugin info..."] + + /api/v1/tornjak/selectors: + get: + summary: Get list of Tornjak selectors. + description: Retrieves a list of Tornjak selectors including agent details. + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + agents: + type: object + properties: + spiffeid: + type: string + examples: ["spiffe://example.org/spire/agent/"] + plugin: + type: string + examples: ["plugin1"] + post: + summary: Post Tornjak selectors. + description: Submits a selector to the Tornjak server. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: object + properties: + plugin: + type: string + examples: ["plugin1"] + spiffe id: + type: string + examples: ["spiffe://example.org/spire/agent"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "SUCCESS" + content: + text/plain: + schema: + type: string + examples: ["SUCCESS"] + + /api/v1/tornjak/clusters: + get: + summary: Get list of Tornjak clusters. + description: Retrieves a list of Tornjak clusters, including details such as name, creation time, and associated agents. + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + clusters: + type: array + items: + type: object + properties: + name: + type: string + examples: ["clustername"] + editedName: + type: string + examples: [""] + creationTime: + type: string + examples: ["Feb 08 2023 21:02:10"] + domainName: + type: string + examples: [""] + managedBy: + type: string + examples: [""] + platformType: + type: string + examples: ["Docker"] + agentsList: + type: array + items: + type: string + examples: ["agent1"] + post: + summary: Create a Tornjak selector. + description: Creates a new Tornjak selector with the specified cluster details. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + cluster: + type: object + properties: + name: + type: string + examples: ["clusterName"] + platformType: + type: string + examples: ["Docker"] + agentsList: + type: array + items: + type: string + examples: + - ["agent1", "agent2"] + domainName: + type: string + examples: ["example.org"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "SUCCESS" + content: + text/plain: + schema: + type: string + examples: ["SUCCESS"] + patch: + summary: Update Tornjak selector. + description: Updates the details of a Tornjak selector, including the cluster name, platform type, agent list, and domain name. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + cluster: + type: object + properties: + name: + type: string + examples: ["clusterName"] + editedName: + type: string + examples: ["newClusterName"] + platformType: + type: string + examples: ["Docker"] + agentsList: + type: array + items: + type: string + examples: ["agent1"] + domainName: + type: string + examples: ["example.org"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "SUCCESS" + content: + text/plain: + schema: + type: string + examples: ["SUCCESS"] + delete: + summary: Delete a Tornjak selector. + description: Deletes a Tornjak selector based on the provided cluster name. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + cluster: + type: object + properties: + name: + type: string + examples: ["clusterName"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "SUCCESS" + content: + text/plain: + schema: + type: string + examples: ["SUCCESS"] + +components: + schemas: + + spiffe_id: + type: object + properties: + path: + type: string + trust_domain: + type: string + + selector: + type: object + properties: + type: + type: string + examples: [k8s_sat] + value: + type: string + + entry: + type: object + properties: + id: + type: string + spiffe_id: + $ref: '#/components/schemas/spiffe_id' + parent_id: + $ref: '#/components/schemas/spiffe_id' + selectors: + type: array + items: + $ref: '#/components/schemas/selector' + + + agent: + type: object + properties: + id: + $ref: '#/components/schemas/spiffe_id' + attestation_type: + type: string + examples: [k8s_sat] + x509svid_serial_number: + type: integer + x509svid_expires_at: + type: integer + selectors: + type: array + items: + $ref: '#/components/schemas/selector' + error: + type: string + examples: ["Bad request"] \ No newline at end of file From a3d7e11c8b0b128c4fb4674bd9a65d4df5008a2b Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:20:13 -0400 Subject: [PATCH 23/39] Updated dev branch (#484) Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- api/agent/server.go | 9 ++++++++- pkg/agent/authorization/rbac.go | 7 ++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index e1602c20..cc5a9e85 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -1061,6 +1061,7 @@ func NewAuthorizer(authorizerPlugin *ast.ObjectItem) (authorization.Authorizer, // decode into role list and apiMapping roleList := make(map[string]string) apiMapping := make(map[string][]string) + apiV1Mapping := make(map[string]map[string][]string) for _, role := range config.RoleList { roleList[role.Name] = role.Desc // print warning for empty string @@ -1077,9 +1078,15 @@ func NewAuthorizer(authorizerPlugin *ast.ObjectItem) (authorization.Authorizer, apiV1.Method = arr[0] apiV1.Path = arr[1] fmt.Printf("API V1 method: %s, API V1 path: %s, API V1 allowed roles: %s \n", apiV1.Method, apiV1.Path, apiV1.AllowedRoles) + if _, ok := apiV1Mapping[apiV1.Path]; ok { + apiV1Mapping[apiV1.Path][apiV1.Method] = apiV1.AllowedRoles + } else { + apiV1Mapping[apiV1.Path] = map[string][]string{apiV1.Method: apiV1.AllowedRoles} + } } + fmt.Printf("API V1 Mapping: %+v\n", apiV1Mapping) - authorizer, err := authorization.NewRBACAuthorizer(config.Name, roleList, apiMapping) + authorizer, err := authorization.NewRBACAuthorizer(config.Name, roleList, apiMapping, apiV1Mapping) if err != nil { return nil, errors.Errorf("Couldn't configure Authorizer: %v", err) } diff --git a/pkg/agent/authorization/rbac.go b/pkg/agent/authorization/rbac.go index 0b1e1fe4..032d419f 100644 --- a/pkg/agent/authorization/rbac.go +++ b/pkg/agent/authorization/rbac.go @@ -3,7 +3,7 @@ package authorization import ( "net/http" "github.com/pkg/errors" - + "fmt" "github.com/spiffe/tornjak/pkg/agent/authentication/user" ) @@ -41,7 +41,7 @@ func validateInitParameters(roleList map[string]string, apiMapping map[string][] if _, ok := staticAPIList[api]; !ok { return errors.Errorf("API %s does not exist", api) } - + // check that each role exists in roleList for _, allowedRole := range allowList { if _, ok := roleList[allowedRole]; !ok { @@ -52,11 +52,12 @@ func validateInitParameters(roleList map[string]string, apiMapping map[string][] return nil } -func NewRBACAuthorizer(policyName string, roleList map[string]string, apiMapping map[string][]string) (*RBACAuthorizer, error) { +func NewRBACAuthorizer(policyName string, roleList map[string]string, apiMapping map[string][]string, apiV1Mapping map[string]map[string][]string) (*RBACAuthorizer, error) { err := validateInitParameters(roleList, apiMapping) if err != nil { return nil, errors.Errorf("Could not parse policy %s: invalid mapping: %v", policyName, err) } + fmt.Printf("apiV1Mapping: %v\n", apiV1Mapping) return &RBACAuthorizer{ name: policyName, roleList: roleList, From 554e050b40562a8e6497ec1bb56850def2c221b5 Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:13:44 -0400 Subject: [PATCH 24/39] Added validation for v1 API config in RBAC plugin (#485) * api v1 mapping Signed-off-by: Xuhang Cao * api v1 mapping Signed-off-by: Xuhang Cao --------- Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- docs/conf/agent/full.conf | 11 ++++++++--- pkg/agent/authorization/rbac.go | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/docs/conf/agent/full.conf b/docs/conf/agent/full.conf index e5f63134..c236a202 100644 --- a/docs/conf/agent/full.conf +++ b/docs/conf/agent/full.conf @@ -89,24 +89,29 @@ plugins { API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/spire/serverinfo" { allowed_roles = ["admin", "viewer"] } APIv1 "GET /api/v1/spire/healthcheck" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/spire/entries" { allowed_roles = ["admin", "viewer"] } APIv1 "GET /api/v1/spire/agents" { allowed_roles = ["admin", "viewer"] } - APIv1 "POST /api/v1/spire/agents/ban" { allowed_roles = ["admin"] } APIv1 "DELETE /api/v1/spire/agents" { allowed_roles = ["admin"] } + APIv1 "POST /api/v1/spire/agents/ban" { allowed_roles = ["admin"] } APIv1 "POST /api/v1/spire/agents/jointoken" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/spire/entries" { allowed_roles = ["admin", "viewer"] } APIv1 "POST /api/v1/spire/entries" { allowed_roles = ["admin"] } APIv1 "DELETE /api/v1/spire/entries" { allowed_roles = ["admin"] } + + # SPIRE Federation API calls APIv1 "GET /api/v1/spire/bundle" { allowed_roles = ["admin", "viewer"] } APIv1 "GET /api/v1/spire/federations/bundles" { allowed_roles = ["admin", "viewer"] } APIv1 "POST /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } APIv1 "PATCH /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } APIv1 "DELETE /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + + # Tornjak API calls APIv1 "GET /api/v1/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/tornjak/agents" { allowed_roles = ["admin", "viewer"] } APIv1 "POST /api/v1/tornjak/selectors" { allowed_roles = ["admin"] } APIv1 "GET /api/v1/tornjak/selectors" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/tornjak/agents" { allowed_roles = ["admin", "viewer"] } APIv1 "GET /api/v1/tornjak/clusters" { allowed_roles = ["admin", "viewer"] } APIv1 "POST /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } APIv1 "PATCH /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } diff --git a/pkg/agent/authorization/rbac.go b/pkg/agent/authorization/rbac.go index 032d419f..ecaf8971 100644 --- a/pkg/agent/authorization/rbac.go +++ b/pkg/agent/authorization/rbac.go @@ -34,8 +34,22 @@ var staticAPIList = map[string]struct{}{ "/api/tornjak/clusters/edit": {}, "/api/tornjak/clusters/delete": {}, } +var staticAPIV1List = map[string]map[string]struct{}{ + "/api/v1/spire/serverinfo" :{"GET": {}}, + "/api/v1/spire/healthcheck" :{"GET": {}}, + "/api/v1/spire/entries" :{"GET": {}, "POST": {}, "DELETE": {}}, + "/api/v1/spire/agents" :{"GET": {}, "POST": {}, "DELETE": {}}, + "/api/v1/spire/agent/ban" :{"POST": {}}, + "/api/v1/spire/agents/jointoken" :{"POST": {}}, + "/api/v1/tornjak/clusters" :{"GET": {}, "POST": {}, "PATCH": {}, "DELETE": {}}, + "/api/v1/tornjak/selectors" :{"GET": {}, "POST": {}}, + "/api/v1/tornjak/agents" :{"GET": {}}, + "/api/v1/tornjak/serverinfo" :{"GET": {}}, + "/api/v1/spire/bundle" :{"GET": {}}, + "/api/v1/spire/federations/bundles" :{"GET": {}, "POST": {}, "DELETE": {}, "PATCH": {}}, +} -func validateInitParameters(roleList map[string]string, apiMapping map[string][]string) error { +func validateInitParameters(roleList map[string]string, apiMapping map[string][]string, apiV1Mapping map[string]map[string][]string) error { for api, allowList := range apiMapping { // check that API exists if _, ok := staticAPIList[api]; !ok { @@ -49,11 +63,26 @@ func validateInitParameters(roleList map[string]string, apiMapping map[string][] } } } + for path, method_dict := range apiV1Mapping { + for method, allowList := range method_dict { + // check that API exists + if _, ok := staticAPIV1List[path][method]; !ok { + return errors.Errorf("API path %s does not exist with method %s", path, method) + } + + // check that each role exists in roleList + for _, allowedRole := range allowList { + if _, ok := roleList[allowedRole]; !ok { + return errors.Errorf("API %s lists undefined role %s", path, allowedRole) + } + } + } + } return nil } func NewRBACAuthorizer(policyName string, roleList map[string]string, apiMapping map[string][]string, apiV1Mapping map[string]map[string][]string) (*RBACAuthorizer, error) { - err := validateInitParameters(roleList, apiMapping) + err := validateInitParameters(roleList, apiMapping, apiV1Mapping) if err != nil { return nil, errors.Errorf("Could not parse policy %s: invalid mapping: %v", policyName, err) } From 71a11318901798a9eb0f1031d232ad95abbcb666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:44:36 -0400 Subject: [PATCH 25/39] Bump webpack from 5.89.0 to 5.94.0 in /frontend (#486) Bumps [webpack](https://github.com/webpack/webpack) from 5.89.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.89.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 140 +++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 75 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aad4bc50..40e28a22 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5163,15 +5163,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -5806,9 +5797,9 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -5825,9 +5816,9 @@ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", @@ -5845,14 +5836,14 @@ "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -5877,26 +5868,26 @@ "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -5904,22 +5895,22 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -5928,11 +5919,11 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -5990,10 +5981,10 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "peerDependencies": { "acorn": "^8" } @@ -9277,9 +9268,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -21256,9 +21247,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -21289,33 +21280,32 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { From b2b997f5fbf385dcb72aaea5d88e5f37e2281545 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:15:38 -0400 Subject: [PATCH 26/39] Bump micromatch from 4.0.5 to 4.0.8 in /frontend (#487) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 40e28a22..f0f48c5b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15129,11 +15129,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { From e8845c4e396a5fba46fc6dfeff7840a5fdd1197e Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Tue, 3 Sep 2024 11:03:35 -0400 Subject: [PATCH 27/39] Add v1 API logic to RBAC plugin (#488) * add initial test file Signed-off-by: Maia Iyer * Added rbac v1 checks Signed-off-by: Maia Iyer * fixed agents ban in rbac v1 list Signed-off-by: Maia Iyer * nit fix Signed-off-by: Maia Iyer * nit fix Signed-off-by: Maia Iyer --------- Signed-off-by: Maia Iyer --- pkg/agent/authorization/rbac.go | 72 ++++++++++++++++++++++++---- pkg/agent/authorization/rbac_test.go | 15 ++++++ 2 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 pkg/agent/authorization/rbac_test.go diff --git a/pkg/agent/authorization/rbac.go b/pkg/agent/authorization/rbac.go index ecaf8971..be1e9edf 100644 --- a/pkg/agent/authorization/rbac.go +++ b/pkg/agent/authorization/rbac.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/pkg/errors" "fmt" + "strings" "github.com/spiffe/tornjak/pkg/agent/authentication/user" ) @@ -11,6 +12,7 @@ type RBACAuthorizer struct { name string roleList map[string]string apiMapping map[string][]string + apiV1Mapping map[string]map[string][]string } // TODO put this in a common constants file @@ -39,7 +41,7 @@ var staticAPIV1List = map[string]map[string]struct{}{ "/api/v1/spire/healthcheck" :{"GET": {}}, "/api/v1/spire/entries" :{"GET": {}, "POST": {}, "DELETE": {}}, "/api/v1/spire/agents" :{"GET": {}, "POST": {}, "DELETE": {}}, - "/api/v1/spire/agent/ban" :{"POST": {}}, + "/api/v1/spire/agents/ban" :{"POST": {}}, "/api/v1/spire/agents/jointoken" :{"POST": {}}, "/api/v1/tornjak/clusters" :{"GET": {}, "POST": {}, "PATCH": {}, "DELETE": {}}, "/api/v1/tornjak/selectors" :{"GET": {}, "POST": {}}, @@ -50,6 +52,9 @@ var staticAPIV1List = map[string]map[string]struct{}{ } func validateInitParameters(roleList map[string]string, apiMapping map[string][]string, apiV1Mapping map[string]map[string][]string) error { + if roleList == nil { + return errors.Errorf("No roles defined") + } for api, allowList := range apiMapping { // check that API exists if _, ok := staticAPIList[api]; !ok { @@ -67,13 +72,13 @@ func validateInitParameters(roleList map[string]string, apiMapping map[string][] for method, allowList := range method_dict { // check that API exists if _, ok := staticAPIV1List[path][method]; !ok { - return errors.Errorf("API path %s does not exist with method %s", path, method) + return errors.Errorf("API V1 path %s does not exist with method %s", path, method) } // check that each role exists in roleList for _, allowedRole := range allowList { if _, ok := roleList[allowedRole]; !ok { - return errors.Errorf("API %s lists undefined role %s", path, allowedRole) + return errors.Errorf("API V1 %s lists undefined role %s", path, allowedRole) } } } @@ -91,19 +96,42 @@ func NewRBACAuthorizer(policyName string, roleList map[string]string, apiMapping name: policyName, roleList: roleList, apiMapping: apiMapping, + apiV1Mapping: apiV1Mapping, }, nil } -func (a *RBACAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) error { - // if not authenticated fail and return error - if u.AuthenticationError != nil { - return errors.Errorf("Authentication error: %v", u.AuthenticationError) +func (a *RBACAuthorizer) authorizeAPIRequest(r *http.Request, u *user.UserInfo) error { + userRoles := u.Roles + apiPath := r.URL.Path + + allowedRoles := a.apiMapping[apiPath] + + // if no role listed for api, reject + if len(allowedRoles) == 0 { + return errors.New("Unauthorized request") + } + + // check each allowed role + for _, allowedRole := range allowedRoles { + if allowedRole == "" { // all authenticated allowed + return nil + } + for _, role := range userRoles { + // user has role + if role == allowedRole { + return nil + } + } } + return errors.New("Unauthorized Request") +} +func (a *RBACAuthorizer) authorizeAPIV1Request(r *http.Request, u *user.UserInfo) error { userRoles := u.Roles apiPath := r.URL.Path + apiMethod := r.Method - allowedRoles := a.apiMapping[apiPath] + allowedRoles := a.apiV1Mapping[apiPath][apiMethod] // if no role listed for api, reject if len(allowedRoles) == 0 { @@ -122,6 +150,32 @@ func (a *RBACAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) err } } } + return errors.New("Unauthorized Request") +} - return errors.New("Unauthorized request") +func (a *RBACAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) error { + // if not authenticated fail and return error + if u.AuthenticationError != nil { + return errors.Errorf("Authentication error: %v", u.AuthenticationError) + } + + // based on path + apiPath := r.URL.Path + isV1 := strings.HasPrefix(apiPath, "/api/v1") + + if !isV1 { + // check old API Request + err := a.authorizeAPIRequest(r, u) + if err != nil { + return errors.Errorf("Tornjak API Authorization error: %v", err) + } + } else { + // check API V1 Request + err := a.authorizeAPIV1Request(r, u) + if err != nil { + return errors.Errorf("Tornjak API V1 Authorization error: %v", err) + } + } + + return nil } diff --git a/pkg/agent/authorization/rbac_test.go b/pkg/agent/authorization/rbac_test.go new file mode 100644 index 00000000..e3992466 --- /dev/null +++ b/pkg/agent/authorization/rbac_test.go @@ -0,0 +1,15 @@ +package authorization + +import ( + "testing" + //"github.com/spiffe/tornjak/pkg/agent/authentication/user" +) + +func TestNewRBACAuthorizer(t *testing.T) { + // INIT failures + // fail when no roles defined + _, err := NewRBACAuthorizer("", nil, nil, nil) + if err == nil { + t.Fatal("ERROR: successfully initialized RBAC without roles") + } +} From 03b8b0bf879c47ab66bdf2b2a99f713fb4bb21ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:12:54 -0400 Subject: [PATCH 28/39] Bump axios from 1.6.5 to 1.7.7 in /frontend (#489) Bumps [axios](https://github.com/axios/axios) from 1.6.5 to 1.7.7. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.6.5...v1.7.7) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 10 +++++----- frontend/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f0f48c5b..c1eec82a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,7 +23,7 @@ "@types/file-saver": "^2.0.5", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.3.1", - "axios": "^1.6.0", + "axios": "^1.7.7", "bootstrap": "^5.3.0", "carbon-components": "^10.36.0", "carbon-components-react": "^8.31.3", @@ -6427,11 +6427,11 @@ } }, "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } diff --git a/frontend/package.json b/frontend/package.json index 610aedc8..a18bce5e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@types/file-saver": "^2.0.5", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.3.1", - "axios": "^1.6.0", + "axios": "^1.7.7", "bootstrap": "^5.3.0", "carbon-components": "^10.36.0", "carbon-components-react": "^8.31.3", From e74dea8dacffc65164865937c8b8402153722e65 Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Mon, 9 Sep 2024 12:49:11 -0400 Subject: [PATCH 29/39] Add APIs for SPIRE Federation (#483) * linted Signed-off-by: Maia Iyer * added list function Signed-off-by: Maia Iyer * added other trustdomain apis Signed-off-by: Maia Iyer * Added apis Signed-off-by: Maia Iyer * fixing spacing Signed-off-by: Maia Iyer * Adding workaround for json parsing Signed-off-by: Maia Iyer * add workaround for update federation Signed-off-by: Maia Iyer * updated OpenAPI doc Signed-off-by: Maia Iyer --------- Signed-off-by: Maia Iyer --- api/agent/api.go | 87 ++ api/agent/server.go | 175 ++++ cmd/agent/main.go | 3 +- docs/conf/agent/full.conf | 251 +++--- go.mod | 2 +- go.sum | 4 +- openapi.yaml | 756 +++++++++++++----- .../authenticator/authenticator.go | 2 +- .../authentication/authenticator/keycloak.go | 19 +- .../authenticator/null_authenticator.go | 2 +- pkg/agent/authentication/user/user.go | 2 +- pkg/agent/authorization/authorization.go | 2 +- pkg/agent/authorization/null_authorizer.go | 4 +- pkg/agent/authorization/rbac.go | 29 +- 14 files changed, 964 insertions(+), 374 deletions(-) diff --git a/api/agent/api.go b/api/agent/api.go index 0a6f9287..f3bcf74d 100644 --- a/api/agent/api.go +++ b/api/agent/api.go @@ -11,6 +11,7 @@ import ( bundle "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" debugServer "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" entry "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1" + trustdomain "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" types "github.com/spiffe/spire-api-sdk/proto/spire/api/types" "google.golang.org/grpc/health/grpc_health_v1" @@ -322,6 +323,92 @@ func (s *Server) DeleteFederatedBundle(inp DeleteFederatedBundleRequest) (*Delet return (*DeleteFederatedBundleResponse)(bundle), nil } +// Federation APIs +type ListFederationRelationshipsRequest trustdomain.ListFederationRelationshipsRequest +type ListFederationRelationshipsResponse trustdomain.ListFederationRelationshipsResponse + +func (s *Server) ListFederationRelationships(inp ListFederationRelationshipsRequest) (*ListFederationRelationshipsResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := trustdomain.ListFederationRelationshipsRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := trustdomain.NewTrustDomainClient(conn) + + bundle, err := client.ListFederationRelationships(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*ListFederationRelationshipsResponse)(bundle), nil +} + +type CreateFederationRelationshipRequest trustdomain.BatchCreateFederationRelationshipRequest +type CreateFederationRelationshipResponse trustdomain.BatchCreateFederationRelationshipResponse + +func (s *Server) CreateFederationRelationship(inp CreateFederationRelationshipRequest) (*CreateFederationRelationshipResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := trustdomain.BatchCreateFederationRelationshipRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := trustdomain.NewTrustDomainClient(conn) + + bundle, err := client.BatchCreateFederationRelationship(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*CreateFederationRelationshipResponse)(bundle), nil +} + +type UpdateFederationRelationshipRequest trustdomain.BatchUpdateFederationRelationshipRequest +type UpdateFederationRelationshipResponse trustdomain.BatchUpdateFederationRelationshipResponse + +func (s *Server) UpdateFederationRelationship(inp UpdateFederationRelationshipRequest) (*UpdateFederationRelationshipResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := trustdomain.BatchUpdateFederationRelationshipRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := trustdomain.NewTrustDomainClient(conn) + + bundle, err := client.BatchUpdateFederationRelationship(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*UpdateFederationRelationshipResponse)(bundle), nil +} + +type DeleteFederationRelationshipRequest trustdomain.BatchDeleteFederationRelationshipRequest +type DeleteFederationRelationshipResponse trustdomain.BatchDeleteFederationRelationshipResponse + +func (s *Server) DeleteFederationRelationship(inp DeleteFederationRelationshipRequest) (*DeleteFederationRelationshipResponse, error) { //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + inpReq := trustdomain.BatchDeleteFederationRelationshipRequest(inp) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + var conn *grpc.ClientConn + conn, err := grpc.Dial(s.SpireServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + client := trustdomain.NewTrustDomainClient(conn) + + bundle, err := client.BatchDeleteFederationRelationship(context.Background(), &inpReq) + if err != nil { + return nil, err + } + + return (*DeleteFederationRelationshipResponse)(bundle), nil +} + + /* Agent diff --git a/api/agent/server.go b/api/agent/server.go index cc5a9e85..198a5257 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -20,6 +20,9 @@ import ( "github.com/hashicorp/hcl/hcl/token" "github.com/pkg/errors" + "google.golang.org/protobuf/encoding/protojson" + trustdomain "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" + "github.com/spiffe/tornjak/pkg/agent/authentication/authenticator" "github.com/spiffe/tornjak/pkg/agent/authorization" agentdb "github.com/spiffe/tornjak/pkg/agent/db" @@ -607,6 +610,174 @@ func (s *Server) federatedBundleDelete(w http.ResponseWriter, r *http.Request) { } } +// Federation APIs +func (s *Server) federationList(w http.ResponseWriter, r *http.Request) { + var input ListFederationRelationshipsRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = ListFederationRelationshipsRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.ListFederationRelationships(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federationCreate(w http.ResponseWriter, r *http.Request) { + var input CreateFederationRelationshipRequest + var rawInput trustdomain.BatchCreateFederationRelationshipRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = CreateFederationRelationshipRequest{} + } else { + // required to use protojson because of oneof field + err := protojson.Unmarshal([]byte(data), &rawInput) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + input = CreateFederationRelationshipRequest(rawInput) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + } + + ret, err := s.CreateFederationRelationship(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federationUpdate(w http.ResponseWriter, r *http.Request) { + var input UpdateFederationRelationshipRequest + var rawInput trustdomain.BatchUpdateFederationRelationshipRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = UpdateFederationRelationshipRequest{} + } else { + err := protojson.Unmarshal([]byte(data), &rawInput) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + input = UpdateFederationRelationshipRequest(rawInput) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + + } + + ret, err := s.UpdateFederationRelationship(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + +func (s *Server) federationDelete(w http.ResponseWriter, r *http.Request) { + var input DeleteFederationRelationshipRequest + buf := new(strings.Builder) + + n, err := io.Copy(buf, r.Body) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + data := buf.String() + + if n == 0 { + input = DeleteFederationRelationshipRequest{} + } else { + err := json.Unmarshal([]byte(data), &input) + if err != nil { + emsg := fmt.Sprintf("Error parsing data: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } + } + + ret, err := s.DeleteFederationRelationship(input) //nolint:govet //Ignoring mutex (not being used) - sync.Mutex by value is unused for linter govet + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusInternalServerError) + return + } + + cors(w, r) + je := json.NewEncoder(w) + err = je.Encode(ret) + if err != nil { + emsg := fmt.Sprintf("Error: %v", err.Error()) + retError(w, emsg, http.StatusBadRequest) + return + } +} + + func cors(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Header().Set("Access-Control-Allow-Origin", "*") @@ -813,6 +984,10 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleCreate).Methods("POST") apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleUpdate).Methods("PATCH") apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleDelete).Methods("DELETE") + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationList).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationCreate).Methods("POST") + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationUpdate).Methods("PATCH") + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationDelete).Methods("DELETE") // Tornjak specific apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo).Methods("GET") diff --git a/cmd/agent/main.go b/cmd/agent/main.go index f62f2ff0..7b5471d2 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -15,7 +15,7 @@ import ( type cliOptions struct { genericOptions struct { - spireFile string + spireFile string tornjakFile string expandEnv bool } @@ -124,7 +124,6 @@ func GetServerInfo(configData string) (agentapi.TornjakSpireServerInfo, error) { return agentapi.TornjakSpireServerInfo{}, errors.Errorf("Could not parse SPIRE Config: %v", err) } - if config.Plugins == nil { return agentapi.TornjakSpireServerInfo{}, errors.New("config plugins map should not be nil") } diff --git a/docs/conf/agent/full.conf b/docs/conf/agent/full.conf index c236a202..3d8bae30 100644 --- a/docs/conf/agent/full.conf +++ b/docs/conf/agent/full.conf @@ -1,125 +1,126 @@ -server { - # location of SPIRE socket - # here, set to default SPIRE socket path - spire_socket_path = "unix:///tmp/spire-server/private/api.sock" - - ### BEGIN SERVER CONNECTION CONFIGURATION ### - # Note: at least one of http, tls, and mtls must be configured - # The server can open multiple if multiple sections included - # The server only ends when all connections error - - # [required] configure HTTP connection to Tornjak server - http { - port = 10000 # container port for HTTP connection - } - - # [optional, recommended] configure HTTPS connection to Tornjak server - https { - port = 10443 # [required for HTTPS] container port for HTTPS connection - cert = "sample-keys/tls.pem" # [required for HTTPS] TLS cert - key = "sample-keys/key.pem" # [required for HTTPS] TLS key - client_ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port - } - - ### END SERVER CONNECTION CONFIGURATION ### -} - -plugins { - - ### BEGIN DATASTORE PLUGIN CONFIGURATION ### - - # Configure SQL local database for Tornjak - DataStore "sql" { - plugin_data { - drivername = "sqlite3" - filename = "tornjak.sqlite3" # location of database - } - } - - ### END DATASTORE PLUGIN CONFIGURATION - - ### BEGIN IAM PLUGIN CONFIGURATION ### - # Note: if no UserManagement configuration included, authentication treated as noop - - # This plugin will extract roles from `realm_access.roles` in the JWT and pass - # to the authorization layer as user roles. - Authenticator "Keycloak" { - plugin_data { - # issuer - Issuer URL for OIDC - # here is a sample for Keycloak running locally on Minikube - issuer = "http://host.docker.internal:8080/realms/tornjak" - # for cloud deployment it would be something like: - # issuer = "http:///realms/tornjak" - - # audience - expected value for aud claim in JWT - # if not included or set, there will be no audience check - # recommended to ensure JWT was meant for Tornjak Backend resource server - audience = "tornjak-backend" - } - } - - # This policy requires admin role for all write calls, viewer role for all read calls - # and authentication success for the "/" api - Authorizer "RBAC" { - plugin_data { - name = "Admin Viewer Policy" - role "admin" { desc = "admin person" } - role "viewer" { desc = "viewer person" } - # this special character role is reserved for allowing all authenticated persons - role "" { desc = "authenticated person" } - - # home tornjak backend api allowed with any successful authentication - API "/" { allowed_roles = [""] } - # allowed with successful authentication and either admin or viewer role - API "/api/healthcheck" { allowed_roles = ["admin", "viewer"] } - API "/api/debugserver" { allowed_roles = ["admin", "viewer"] } - API "/api/agent/list" { allowed_roles = ["admin", "viewer"] } - API "/api/entry/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/selectors/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/agents/list" { allowed_roles = ["admin", "viewer"] } - API "/api/tornjak/clusters/list" { allowed_roles = ["admin", "viewer"] } - # allowed with successful authentication and admin role - API "/api/agent/ban" { allowed_roles = ["admin"] } - API "/api/agent/delete" { allowed_roles = ["admin"] } - API "/api/agent/createjointoken" { allowed_roles = ["admin"] } - API "/api/entry/create" { allowed_roles = ["admin"] } - API "/api/entry/delete" { allowed_roles = ["admin"] } - API "/api/tornjak/selectors/register" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } - API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } - - APIv1 "GET /api/v1/spire/serverinfo" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/spire/healthcheck" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/spire/agents" { allowed_roles = ["admin", "viewer"] } - APIv1 "DELETE /api/v1/spire/agents" { allowed_roles = ["admin"] } - APIv1 "POST /api/v1/spire/agents/ban" { allowed_roles = ["admin"] } - APIv1 "POST /api/v1/spire/agents/jointoken" { allowed_roles = ["admin"] } - APIv1 "GET /api/v1/spire/entries" { allowed_roles = ["admin", "viewer"] } - APIv1 "POST /api/v1/spire/entries" { allowed_roles = ["admin"] } - APIv1 "DELETE /api/v1/spire/entries" { allowed_roles = ["admin"] } - - # SPIRE Federation API calls - APIv1 "GET /api/v1/spire/bundle" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/spire/federations/bundles" { allowed_roles = ["admin", "viewer"] } - APIv1 "POST /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } - APIv1 "PATCH /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } - APIv1 "DELETE /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } - - # Tornjak API calls - APIv1 "GET /api/v1/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/tornjak/agents" { allowed_roles = ["admin", "viewer"] } - APIv1 "POST /api/v1/tornjak/selectors" { allowed_roles = ["admin"] } - APIv1 "GET /api/v1/tornjak/selectors" { allowed_roles = ["admin", "viewer"] } - APIv1 "GET /api/v1/tornjak/clusters" { allowed_roles = ["admin", "viewer"] } - APIv1 "POST /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } - APIv1 "PATCH /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } - APIv1 "DELETE /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } - } - } - - ### END IAM PLUGIN CONFIGURATION - - -} +server { + # location of SPIRE socket + # here, set to default SPIRE socket path + spire_socket_path = "unix:///tmp/spire-server/private/api.sock" + + ### BEGIN SERVER CONNECTION CONFIGURATION ### + # Note: at least one of http, tls, and mtls must be configured + # The server can open multiple if multiple sections included + # The server only ends when all connections error + + # [required] configure HTTP connection to Tornjak server + http { + port = 10000 # container port for HTTP connection + } + + # [optional, recommended] configure HTTPS connection to Tornjak server + https { + port = 10443 # [required for HTTPS] container port for HTTPS connection + cert = "sample-keys/tls.pem" # [required for HTTPS] TLS cert + key = "sample-keys/key.pem" # [required for HTTPS] TLS key + client_ca = "sample-keys/rootCA.pem" # enables mTLS connection for HTTPS port + } + + ### END SERVER CONNECTION CONFIGURATION ### +} + +plugins { + + ### BEGIN DATASTORE PLUGIN CONFIGURATION ### + + # Configure SQL local database for Tornjak + DataStore "sql" { + plugin_data { + drivername = "sqlite3" + filename = "tornjak.sqlite3" # location of database + } + } + + ### END DATASTORE PLUGIN CONFIGURATION + + ### BEGIN IAM PLUGIN CONFIGURATION ### + # Note: if no UserManagement configuration included, authentication treated as noop + + # This plugin will extract roles from `realm_access.roles` in the JWT and pass + # to the authorization layer as user roles. + Authenticator "Keycloak" { + plugin_data { + # issuer - Issuer URL for OIDC + # here is a sample for Keycloak running locally on Minikube + issuer = "http://host.docker.internal:8080/realms/tornjak" + # for cloud deployment it would be something like: + # issuer = "http:///realms/tornjak" + + # audience - expected value for aud claim in JWT + # if not included or set, there will be no audience check + # recommended to ensure JWT was meant for Tornjak Backend resource server + audience = "tornjak-backend" + } + } + + # This policy requires admin role for all write calls, viewer role for all read calls + # and authentication success for the "/" api + Authorizer "RBAC" { + plugin_data { + name = "Admin Viewer Policy" + role "admin" { desc = "admin person" } + role "viewer" { desc = "viewer person" } + # this special character role is reserved for allowing all authenticated persons + role "" { desc = "authenticated person" } + + # home tornjak backend api allowed with any successful authentication + API "/" { allowed_roles = [""] } + # allowed with successful authentication and either admin or viewer role + API "/api/healthcheck" { allowed_roles = ["admin", "viewer"] } + API "/api/debugserver" { allowed_roles = ["admin", "viewer"] } + API "/api/agent/list" { allowed_roles = ["admin", "viewer"] } + API "/api/entry/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/selectors/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/agents/list" { allowed_roles = ["admin", "viewer"] } + API "/api/tornjak/clusters/list" { allowed_roles = ["admin", "viewer"] } + # allowed with successful authentication and admin role + API "/api/agent/ban" { allowed_roles = ["admin"] } + API "/api/agent/delete" { allowed_roles = ["admin"] } + API "/api/agent/createjointoken" { allowed_roles = ["admin"] } + API "/api/entry/create" { allowed_roles = ["admin"] } + API "/api/entry/delete" { allowed_roles = ["admin"] } + API "/api/tornjak/selectors/register" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } + API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } + + # v1 API + APIv1 "GET /api/v1/spire/serverinfo" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/healthcheck" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/agents" { allowed_roles = ["admin", "viewer"] } + APIv1 "DELETE /api/v1/spire/agents" { allowed_roles = ["admin"] } + APIv1 "POST /api/v1/spire/agents/ban" { allowed_roles = ["admin"] } + APIv1 "POST /api/v1/spire/agents/jointoken" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/spire/entries" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/spire/entries" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/spire/entries" { allowed_roles = ["admin"] } + + # SPIRE Federation API calls + APIv1 "GET /api/v1/spire/bundle" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/spire/federations/bundles" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + APIv1 "PATCH /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/spire/federations/bundles" { allowed_roles = ["admin"] } + + # Tornjak API calls + APIv1 "GET /api/v1/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/tornjak/agents" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/tornjak/selectors" { allowed_roles = ["admin"] } + APIv1 "GET /api/v1/tornjak/selectors" { allowed_roles = ["admin", "viewer"] } + APIv1 "GET /api/v1/tornjak/clusters" { allowed_roles = ["admin", "viewer"] } + APIv1 "POST /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + APIv1 "PATCH /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + APIv1 "DELETE /api/v1/tornjak/clusters" { allowed_roles = ["admin"] } + } + } + + ### END IAM PLUGIN CONFIGURATION + + +} diff --git a/go.mod b/go.mod index 8d9451d7..76cb820c 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/spiffe/spire-api-sdk v1.2.5-0.20230413135745-699e242b965d github.com/urfave/cli/v2 v2.3.0 google.golang.org/grpc v1.56.3 + google.golang.org/protobuf v1.34.2 ) require ( @@ -57,5 +58,4 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.19.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index e81a7ed1..5df51d15 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/openapi.yaml b/openapi.yaml index f1be170e..06d5e66d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4,7 +4,7 @@ info: description: | This API allows management of SPIRE server via the Tornjak server, comprised of SPIRE server API calls and Tornjak-specific API calls. - version: 1.3.0 + version: "1.8.0" paths: /api/v1/spire/healthcheck: get: @@ -107,6 +107,23 @@ paths: "uptime": 333, "federated_bundles_count": 1 } + /api/v1/spire/bundle: + get: + summary: Get current SPIRE server bundle + description: Retrieves SPIRE server bundle + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + $ref: '#/components/schemas/bundle' /api/v1/spire/agents: get: summary: Calls SPIRE server `spire-server agent list` command @@ -129,30 +146,7 @@ paths: type: array items: $ref: '#/components/schemas/agent' - example: # Example response - { - "agents": [ - { - "id": { - "trust_domain": "example.org", - "path": "/spire/agent" - }, - "attestation_type": "k8s_sat", - "x509svid_serial_number": "111", - "x509svid_expires_at": 222, - "selectors": [ - { - "type": "k8s_sat", - "value": "agent_ns:spire" - }, - { - "type": "k8s_sat", - "value": "agent_sa:spire-agent" - } - ] - } - ] - } + delete: summary: Calls SPIRE server `spire-server agent evict` command @@ -192,13 +186,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/spiffe_id' - examples: - example_request: - value: - id: - path: "/spire/agent/" - trust_domain: "example.org" + type: object + properties: + id: + $ref: '#/components/schemas/spiffe_id' responses: default: description: "Unexpected error" @@ -236,13 +227,6 @@ paths: ttl: type: integer examples: [500] - examples: - example1: - value: - path: "/sample/spiffe/id" - token: 1111 - trust_domain: "example.org" - ttl: 500 responses: default: description: "Unexpected error" @@ -263,11 +247,6 @@ paths: expires_at: type: integer examples: [555] - examples: - example1: - value: - value: 1111 - expires_at: 555 /api/v1/spire/entries: get: summary: Calls SPIRE server `spire-server entry show` @@ -284,26 +263,13 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/entry' - examples: - example_response: - summary: "A sample response showing entries" - value: - entries: - - id: "id1" - spiffe_id: - trust_domain: "example.org" - path: "/spire/agent/" - parent_id: - trust_domain: "example.org" - path: "/spire/agent/" - selectors: - - type: "k8s_sat" - value: "agent_ns:spire" - - type: "k8s_sat" - value: "agent_sa:spire-agent" + type: object + properties: + entries: + type: array + items: + $ref: '#/components/schemas/entry' + post: summary: Calls SPIRE server `spire-server entry create` description: Create registration entries @@ -312,43 +278,13 @@ paths: content: application/json: schema: - allOf: - - $ref: '#/components/schemas/entry' - - type: object - properties: - admin: - type: string - dns_names: - type: string - downstream: - type: string - expires_at: - type: string - federates_with: - type: array - items: - type: string - examples: [] - examples: - example_request: - summary: "Example request for creating a registration entry" - value: - admin: "/spire/agent/" - dns_names: "/spire/agent/" - downstream: "/spire/agent/" - expires_at: "/spire/agent/" - federates_with: [] - parent_id: - trust_domain: "example.org" - path: "/spire/agent/" - selectors: - - type: "k8s_sat" - value: "agent_ns:spire" - - type: "k8s_sat" - value: "agent_sa:spire-agent" - spiffe_id: - trust_domain: "example.org" - path: "/spire/agent/" + type: object + properties: + entries: + type: array + items: + $ref: '#/components/schemas/entry' + responses: default: description: "Unexpected error" @@ -367,34 +303,12 @@ paths: type: array items: type: object - properties: - status: - type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object properties: - message: - type: string - examples: ["OK"] - entry: - $ref: '#/components/schemas/entry' - examples: - example_response: - summary: "Example response after creating a registration entry" - value: - results: - - status: - message: "OK" - entry: - spiffe_id: - trust_domain: "example.org" - path: "/spire/agent/" - parent_id: - trust_domain: "example.org" - path: "/spire/agent/" - selectors: - - type: "k8s_sat" - value: "agent_ns:spire" - - type: "k8s_sat" - value: "agent_sa:spire-agent" + entry: + $ref: '#/components/schemas/entry' delete: summary: Calls SPIRE server `spire-server entry delete` command description: Deletes a specified registration entry @@ -409,11 +323,7 @@ paths: type: array items: type: string - examples: - - summary: "Example request to delete a registration entry" - value: - ids: - - "111" + examples: ["858da-3d-40-b7-caea9"] responses: default: description: "Unexpected error" @@ -428,31 +338,293 @@ paths: schema: type: object properties: - Results: + results: type: array items: type: object - properties: - status: - type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object properties: - message: + id: type: string examples: - - "OK" - id: - type: string - examples: - - "111" - examples: - example_response: - summary: "Example response after deleting a registration entry" - value: - Results: - - status: - message: "OK" - id: "111" - + - "858da-3d-40-b7-caea9" + /api/v1/spire/federations: + get: + summary: Lists all federations configured on SPIRE Server + description: Lists all federations configured on SPIRE Server + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + federation_relationships: + type: array + items: + $ref: '#/components/schemas/federation_response' + post: + summary: Creates federation relationship on SPIRE server + description: Calls `spire-server federation create` + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + federation_relationships: + type: array + items: + $ref: '#/components/schemas/federation_request' + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + federation_relationship: + $ref: '#/components/schemas/federation_response' + patch: + summary: Creates federation relationship on SPIRE server + description: Calls `spire-server federation create` + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + federation_relationships: + type: array + items: + $ref: '#/components/schemas/federation_request' + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + federation_relationship: + $ref: '#/components/schemas/federation_response' + delete: + summary: Deletes federation relationship on SPIRE server + description: Calls `spire-server federation delete` + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + trust_domains: + type: array + items: + type: string + examples: ["example.org"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + trust_domain: + type: string + examples: ["example.org"] + /api/v1/spire/federations/bundles: + get: + summary: Lists federation bundles + description: Call `spire-server bundle list` + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + oneOf: + - type: object + properties: + bundles: + type: array + items: + $ref: '#/components/schemas/bundle' + - type: object # if no bundles, it is empty + post: + summary: Sets federation bundles + description: Call `spire-server bundle set` + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + bundle: + type: array + items: + $ref: '#/components/schemas/bundle' + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + bundle: + $ref: '#/components/schemas/bundle' + patch: + summary: Sets federation bundles + description: Updates if bundle found, else returns error + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + bundle: + type: array + items: + $ref: '#/components/schemas/bundle' + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + bundle: + $ref: '#/components/schemas/bundle' + delete: + requestBody: + content: + application/json: + schema: + type: object + properties: + trust_domains: + type: array + items: + type: string + examples: ["example.org"] + responses: + default: + description: "Unexpected error" + content: + application/json: + schema: + $ref: '#/components/schemas/error' + "200": + description: "OK" + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + allOf: + - $ref: '#/components/schemas/spire_status' + - type: object + properties: + trust_domain: + type: string + examples: ["trust_domain"] /api/v1/tornjak/serverinfo: get: summary: Get general Tornjak server information. @@ -517,14 +689,19 @@ paths: type: object properties: agents: - type: object - properties: - spiffeid: - type: string - examples: ["spiffe://example.org/spire/agent/"] - plugin: - type: string - examples: ["plugin1"] + type: array + items: + type: object + properties: + spiffeid: + type: string + examples: ["spiffe://example.org/spire/agent/"] + plugin: + type: string + examples: ["plugin1"] + cluster: + type: string + examples: [""] post: summary: Post Tornjak selectors. description: Submits a selector to the Tornjak server. @@ -535,15 +712,12 @@ paths: schema: type: object properties: - id: - type: object - properties: - plugin: - type: string - examples: ["plugin1"] - spiffe id: - type: string - examples: ["spiffe://example.org/spire/agent"] + plugin: + type: string + examples: ["plugin1"] + spiffe id: + type: string + examples: ["spiffe://example.org/spire/agent"] responses: default: description: "Unexpected error" @@ -581,33 +755,10 @@ paths: type: array items: type: object - properties: - name: - type: string - examples: ["clustername"] - editedName: - type: string - examples: [""] - creationTime: - type: string - examples: ["Feb 08 2023 21:02:10"] - domainName: - type: string - examples: [""] - managedBy: - type: string - examples: [""] - platformType: - type: string - examples: ["Docker"] - agentsList: - type: array - items: - type: string - examples: ["agent1"] + $ref: '#/components/schemas/tornjak_cluster' post: - summary: Create a Tornjak selector. - description: Creates a new Tornjak selector with the specified cluster details. + summary: Create a Tornjak cluster + description: Creates a new Tornjak cluster. requestBody: required: true content: @@ -617,22 +768,7 @@ paths: properties: cluster: type: object - properties: - name: - type: string - examples: ["clusterName"] - platformType: - type: string - examples: ["Docker"] - agentsList: - type: array - items: - type: string - examples: - - ["agent1", "agent2"] - domainName: - type: string - examples: ["example.org"] + $ref: '#/components/schemas/tornjak_cluster' responses: default: description: "Unexpected error" @@ -724,29 +860,55 @@ paths: components: schemas: + spire_status_ok: + type: object + properties: + message: + type: string + examples: ["OK"] + spire_status_error: + type: object + properties: + code: + type: integer + minimum: 0 + message: + type: string + examples: ["failed to ..."] + spire_status: + type: object + properties: + status: + oneOf: + - $ref: '#/components/schemas/spire_status_ok' + - $ref: '#/components/schemas/spire_status_error' spiffe_id: type: object properties: path: type: string + examples: ["/ns/default"] trust_domain: type: string + examples: ["example.org"] selector: type: object properties: type: type: string - examples: [k8s_sat] + examples: ["k8s_sat"] value: type: string + examples: ["agent_ns:spire", "agent_sa:spire-agent"] entry: type: object properties: id: type: string + examples: ["858da-34-50-b7-cacd98"] spiffe_id: $ref: '#/components/schemas/spiffe_id' parent_id: @@ -755,7 +917,33 @@ components: type: array items: $ref: '#/components/schemas/selector' - + x509_svid_ttl: + type: integer + minimum: 0 + examples: [12345] + created_at: + type: integer + minimum: 0 + examples: [1725556278] + admin: + type: boolean + examples: [false] + dns_names: + type: array + items: + type: string + examples: ["example1.org"] + downstream: + type: boolean + examples: [false] + expires_at: + type: integer + examples: [12345] + federates_with: + type: array + items: + type: string + examples: ["example1.org", "example2.org"] agent: type: object @@ -764,7 +952,7 @@ components: $ref: '#/components/schemas/spiffe_id' attestation_type: type: string - examples: [k8s_sat] + examples: ["k8s_sat"] x509svid_serial_number: type: integer x509svid_expires_at: @@ -773,6 +961,144 @@ components: type: array items: $ref: '#/components/schemas/selector' + + federation_profile_web: + type: object + properties: + https_web: + type: object + federation_profile_web_response: + type: object + properties: + HttpsWeb: + type: object + federation_profile_spiffe: + type: object + properties: + https_spiffe: + type: object + properties: + endpoint_spiffe_id: + type: string + examples: ["spiffe://example1.org/spire-server"] + federation_profile_spiffe_response: + type: object + properties: + HttpsSpiffe: + type: object + properties: + endpoint_spiffe_id: + type: string + examples: ["spiffe://example1.org/spire-server"] + federation_request: + type: object + required: + - trust_domain + - bundle_endpoint_url + allOf: + - type: object + properties: + trust_domain: + type: string + examples: ["example1.org"] + bundle_endpoint_url: + type: string + examples: ["https://bundle.example1.org"] + trust_domain_bundle: + type: object + $ref: '#/components/schemas/bundle' + - oneOf: + - $ref: '#/components/schemas/federation_profile_spiffe' + - $ref: '#/components/schemas/federation_profile_web' + examples: + - { + "trust_domain": "example1.org", + "bundle_endpoint_url": "https://bundle.example1.org", + "https_spiffe": { + "endpoint_spiffe_id": "spiffe://example1.org/spire-server" + } + } + - {"trust_domain": "example2.org", + "bundle_endpoint_url": "https://bundle.example2.org", + "https_web": {} + } + federation_response: + type: object + allOf: + - type: object + properties: + trust_domain: + type: string + examples: ["example1.org"] + bundle_endpoint_url: + type: string + examples: ["https://bundle.example1.org"] + BundleEndpointProfile: + type: object + oneOf: + - $ref: '#/components/schemas/federation_profile_spiffe_response' + - $ref: '#/components/schemas/federation_profile_web_response' + bundle: + type: object + properties: + trust_domain: + type: string + examples: ["example.org"] + x509_authorities: + type: array + items: + type: object + properties: + asn1: + type: string + examples: ["MIIDiz..."] + jwt_authorities: + type: array + items: + type: object + properties: + public_key: + type: string + examples: ["MIIBIj..."] + key_id: + type: string + examples: ["Elaub..."] + expires_at: + type: integer + minimum: 0 + examples: [1723062174] + sequence_number: + type: integer + minimum: 0 + examples: [3] + tornjak_cluster: + type: object + properties: + name: + type: string + examples: ["clusterName"] + platformType: + type: string + examples: ["Docker"] + agentsList: + type: array + items: + type: string + examples: + - "agent1" + - "agent2" + domainName: + type: string + examples: ["example.org"] + editedName: + type: string + examples: [""] + creationTime: + type: string + examples: ["Feb 08 2023 21:02:10"] + managedBy: + type: string + examples: [""] error: type: string - examples: ["Bad request"] \ No newline at end of file + examples: ["Bad request"] diff --git a/pkg/agent/authentication/authenticator/authenticator.go b/pkg/agent/authentication/authenticator/authenticator.go index fc6210fc..aebf4d67 100644 --- a/pkg/agent/authentication/authenticator/authenticator.go +++ b/pkg/agent/authentication/authenticator/authenticator.go @@ -10,5 +10,5 @@ type Authenticator interface { // AuthenticateRequest takes request, verifies certain properties // and returns relevant UserInfo to be interpreted by the Authorizer // or error upon verification error - AuthenticateRequest(r *http.Request) (*user.UserInfo) + AuthenticateRequest(r *http.Request) *user.UserInfo } diff --git a/pkg/agent/authentication/authenticator/keycloak.go b/pkg/agent/authentication/authenticator/keycloak.go index 0a7571fa..6f4ec43b 100644 --- a/pkg/agent/authentication/authenticator/keycloak.go +++ b/pkg/agent/authentication/authenticator/keycloak.go @@ -26,9 +26,9 @@ type KeycloakClaim struct { } type KeycloakAuthenticator struct { - jwks *keyfunc.JWKS - jwksURL string - audience string + jwks *keyfunc.JWKS + jwksURL string + audience string } func getJWKeyFunc(httpjwks bool, jwksInfo string) (*keyfunc.JWKS, error) { @@ -57,7 +57,8 @@ func getJWKeyFunc(httpjwks bool, jwksInfo string) (*keyfunc.JWKS, error) { } // newKeycloakAuthenticator (https bool, jwks string, redirect string) -// get keyfunc based on https +// +// get keyfunc based on https func NewKeycloakAuthenticator(httpjwks bool, issuerURL string, audience string) (*KeycloakAuthenticator, error) { // perform OIDC discovery oidcClient, err := discovery.NewClient(context.Background(), issuerURL) @@ -73,9 +74,9 @@ func NewKeycloakAuthenticator(httpjwks bool, issuerURL string, audience string) return nil, err } return &KeycloakAuthenticator{ - jwks: jwks, - audience: audience, - jwksURL: jwksURL, + jwks: jwks, + audience: audience, + jwksURL: jwksURL, }, nil } @@ -96,13 +97,13 @@ func getToken(r *http.Request, redirectURL string) (string, error) { } -func wrapAuthenticationError(err error) (*user.UserInfo) { +func wrapAuthenticationError(err error) *user.UserInfo { return &user.UserInfo{ AuthenticationError: err, } } -func (a *KeycloakAuthenticator) AuthenticateRequest(r *http.Request)(*user.UserInfo) { +func (a *KeycloakAuthenticator) AuthenticateRequest(r *http.Request) *user.UserInfo { token, err := getToken(r, a.jwksURL) if err != nil { return wrapAuthenticationError(err) diff --git a/pkg/agent/authentication/authenticator/null_authenticator.go b/pkg/agent/authentication/authenticator/null_authenticator.go index 9fadd254..81e8fdcb 100644 --- a/pkg/agent/authentication/authenticator/null_authenticator.go +++ b/pkg/agent/authentication/authenticator/null_authenticator.go @@ -11,6 +11,6 @@ type NullAuthenticator struct{} func NewNullAuthenticator() *NullAuthenticator { return &NullAuthenticator{} } -func (a *NullAuthenticator) AuthenticateRequest(r *http.Request) (*user.UserInfo) { +func (a *NullAuthenticator) AuthenticateRequest(r *http.Request) *user.UserInfo { return nil } diff --git a/pkg/agent/authentication/user/user.go b/pkg/agent/authentication/user/user.go index c5d1c562..765b29f3 100644 --- a/pkg/agent/authentication/user/user.go +++ b/pkg/agent/authentication/user/user.go @@ -2,5 +2,5 @@ package user type UserInfo struct { AuthenticationError error - Roles []string + Roles []string } diff --git a/pkg/agent/authorization/authorization.go b/pkg/agent/authorization/authorization.go index 3bf833e3..0844a418 100644 --- a/pkg/agent/authorization/authorization.go +++ b/pkg/agent/authorization/authorization.go @@ -9,4 +9,4 @@ import ( type Authorizer interface { // Authorize Request AuthorizeRequest(r *http.Request, u *user.UserInfo) error -} \ No newline at end of file +} diff --git a/pkg/agent/authorization/null_authorizer.go b/pkg/agent/authorization/null_authorizer.go index 13a57dde..811f4c43 100644 --- a/pkg/agent/authorization/null_authorizer.go +++ b/pkg/agent/authorization/null_authorizer.go @@ -11,6 +11,6 @@ type NullAuthorizer struct{} func NewNullAuthorizer() *NullAuthorizer { return &NullAuthorizer{} } -func (a *NullAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) (error) { +func (a *NullAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) error { return nil -} \ No newline at end of file +} diff --git a/pkg/agent/authorization/rbac.go b/pkg/agent/authorization/rbac.go index be1e9edf..df01b469 100644 --- a/pkg/agent/authorization/rbac.go +++ b/pkg/agent/authorization/rbac.go @@ -1,31 +1,32 @@ package authorization import ( - "net/http" "github.com/pkg/errors" "fmt" "strings" + "net/http" + "github.com/spiffe/tornjak/pkg/agent/authentication/user" ) type RBACAuthorizer struct { - name string - roleList map[string]string + name string + roleList map[string]string apiMapping map[string][]string apiV1Mapping map[string]map[string][]string } // TODO put this in a common constants file var staticAPIList = map[string]struct{}{ - "/": {}, - "/api/healthcheck": {}, - "/api/debugserver": {}, - "/api/agent/list": {}, - "/api/entry/list": {}, - "/api/tornjak/serverinfo": {}, - "/api/tornjak/selectors/list": {}, - "/api/tornjak/agents/list": {}, - "/api/tornjak/clusters/list": {}, + "/": {}, + "/api/healthcheck": {}, + "/api/debugserver": {}, + "/api/agent/list": {}, + "/api/entry/list": {}, + "/api/tornjak/serverinfo": {}, + "/api/tornjak/selectors/list": {}, + "/api/tornjak/agents/list": {}, + "/api/tornjak/clusters/list": {}, "/api/agent/ban": {}, "/api/agent/delete": {}, "/api/agent/createjointoken": {}, @@ -93,8 +94,8 @@ func NewRBACAuthorizer(policyName string, roleList map[string]string, apiMapping } fmt.Printf("apiV1Mapping: %v\n", apiV1Mapping) return &RBACAuthorizer{ - name: policyName, - roleList: roleList, + name: policyName, + roleList: roleList, apiMapping: apiMapping, apiV1Mapping: apiV1Mapping, }, nil From 5d2b8bebc96516e7d941f1df8ffcc24c634ff65a Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Wed, 11 Sep 2024 15:42:32 -0400 Subject: [PATCH 30/39] Test adding Option method Signed-off-by: Maia Iyer --- api/agent/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index 198a5257..cfe9501f 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -970,8 +970,8 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete) // Spire APIs with versioning - apiRtr.HandleFunc("/api/v1/spire/serverinfo", s.debugServer).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck).Methods("GET") + apiRtr.HandleFunc("/api/v1/spire/serverinfo", s.debugServer).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/agents", s.agentList).Methods("GET") apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan).Methods("POST") apiRtr.HandleFunc("/api/v1/spire/agents", s.agentDelete).Methods("DELETE") @@ -996,7 +996,7 @@ func (s *Server) GetRouter() http.Handler { apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakSelectorsList).Methods("GET") apiRtr.HandleFunc("/api/v1/tornjak/agents", s.tornjakAgentsList).Methods("GET") // Clusters - apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterList).Methods("GET") + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterList).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterCreate).Methods("POST") apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterEdit).Methods("PATCH") apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterDelete).Methods("DELETE") From 0690bcbaec4d1c00f572bdfa2df77d9a34048f7f Mon Sep 17 00:00:00 2001 From: Maia Iyer Date: Wed, 11 Sep 2024 16:08:35 -0400 Subject: [PATCH 31/39] Added options for rest of APIs Signed-off-by: Maia Iyer --- api/agent/server.go | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/api/agent/server.go b/api/agent/server.go index cfe9501f..cbb9a835 100644 --- a/api/agent/server.go +++ b/api/agent/server.go @@ -972,34 +972,34 @@ func (s *Server) GetRouter() http.Handler { // Spire APIs with versioning apiRtr.HandleFunc("/api/v1/spire/serverinfo", s.debugServer).Methods(http.MethodGet, http.MethodOptions) apiRtr.HandleFunc("/api/v1/spire/healthcheck", s.healthcheck).Methods(http.MethodGet, http.MethodOptions) - apiRtr.HandleFunc("/api/v1/spire/agents", s.agentList).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan).Methods("POST") - apiRtr.HandleFunc("/api/v1/spire/agents", s.agentDelete).Methods("DELETE") - apiRtr.HandleFunc("/api/v1/spire/agents/jointoken", s.agentCreateJoinToken).Methods("POST") - apiRtr.HandleFunc("/api/v1/spire/entries", s.entryList).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/entries", s.entryCreate).Methods("POST") - apiRtr.HandleFunc("/api/v1/spire/entries", s.entryDelete).Methods("DELETE") - apiRtr.HandleFunc("/api/v1/spire/bundle", s.bundleGet).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleList).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleCreate).Methods("POST") - apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleUpdate).Methods("PATCH") - apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleDelete).Methods("DELETE") - apiRtr.HandleFunc("/api/v1/spire/federations", s.federationList).Methods("GET") - apiRtr.HandleFunc("/api/v1/spire/federations", s.federationCreate).Methods("POST") - apiRtr.HandleFunc("/api/v1/spire/federations", s.federationUpdate).Methods("PATCH") - apiRtr.HandleFunc("/api/v1/spire/federations", s.federationDelete).Methods("DELETE") + apiRtr.HandleFunc("/api/v1/spire/agents", s.agentList).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/agents/ban", s.agentBan).Methods(http.MethodPost, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/agents", s.agentDelete).Methods(http.MethodDelete, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/agents/jointoken", s.agentCreateJoinToken).Methods(http.MethodPost, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryList).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryCreate).Methods(http.MethodPost) + apiRtr.HandleFunc("/api/v1/spire/entries", s.entryDelete).Methods(http.MethodDelete) + apiRtr.HandleFunc("/api/v1/spire/bundle", s.bundleGet).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleList).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleCreate).Methods(http.MethodPost) + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleUpdate).Methods(http.MethodPatch) + apiRtr.HandleFunc("/api/v1/spire/federations/bundles", s.federatedBundleDelete).Methods(http.MethodDelete) + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationList).Methods(http.MethodGet, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationCreate).Methods(http.MethodPost) + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationUpdate).Methods(http.MethodPatch) + apiRtr.HandleFunc("/api/v1/spire/federations", s.federationDelete).Methods(http.MethodDelete) // Tornjak specific - apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo).Methods("GET") + apiRtr.HandleFunc("/api/v1/tornjak/serverinfo", s.tornjakGetServerInfo).Methods(http.MethodGet, http.MethodOptions) // Agents Selectors - apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakPluginDefine).Methods("POST") - apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakSelectorsList).Methods("GET") - apiRtr.HandleFunc("/api/v1/tornjak/agents", s.tornjakAgentsList).Methods("GET") + apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakPluginDefine).Methods(http.MethodPost, http.MethodOptions) + apiRtr.HandleFunc("/api/v1/tornjak/selectors", s.tornjakSelectorsList).Methods(http.MethodGet) + apiRtr.HandleFunc("/api/v1/tornjak/agents", s.tornjakAgentsList).Methods(http.MethodGet, http.MethodOptions) // Clusters apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterList).Methods(http.MethodGet, http.MethodOptions) - apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterCreate).Methods("POST") - apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterEdit).Methods("PATCH") - apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterDelete).Methods("DELETE") + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterCreate).Methods(http.MethodPost) + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterEdit).Methods(http.MethodPatch) + apiRtr.HandleFunc("/api/v1/tornjak/clusters", s.clusterDelete).Methods(http.MethodDelete) // Middleware apiRtr.Use(s.verificationMiddleware) @@ -1012,7 +1012,7 @@ func (s *Server) GetRouter() http.Handler { } func (s *Server) redirectHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" && r.Method != "HEAD" { + if r.Method != http.MethodGet && r.Method != "HEAD" { http.Error(w, "Use HTTPS", http.StatusBadRequest) return } From c0cdc8a28fac96e8eca74f7b48c61cd5021adf39 Mon Sep 17 00:00:00 2001 From: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:15:21 -0400 Subject: [PATCH 32/39] Added unit tests for RBAC package (#490) * Start writing rbac tests Signed-off-by: Xuhang Cao * Start writing rbac tests Signed-off-by: Xuhang Cao * Start writing rbac tests Signed-off-by: Xuhang Cao * Changed error messages Signed-off-by: Xuhang Cao * Imported strings Signed-off-by: Xuhang Cao * Imported strings Signed-off-by: Xuhang Cao --------- Signed-off-by: Xuhang Cao <65797697+Xiaocao-Cxh@users.noreply.github.com> --- pkg/agent/authorization/rbac_test.go | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/pkg/agent/authorization/rbac_test.go b/pkg/agent/authorization/rbac_test.go index e3992466..e57a4ce2 100644 --- a/pkg/agent/authorization/rbac_test.go +++ b/pkg/agent/authorization/rbac_test.go @@ -2,6 +2,7 @@ package authorization import ( "testing" + "strings" //"github.com/spiffe/tornjak/pkg/agent/authentication/user" ) @@ -12,4 +13,68 @@ func TestNewRBACAuthorizer(t *testing.T) { if err == nil { t.Fatal("ERROR: successfully initialized RBAC without roles") } + + // constants for 4 parameters + policyName := "testPolicy" + roleList_1 := map[string]string{"admin": "admin"} + apiV1Mapping_1 := map[string]map[string][]string{"/api/v1/spire/serverinfo": {"GET": {"admin", "viewer"}}} + + roleList_2 := map[string]string{"admin": "admin", "viewer": "viewer"} + apiV1Mapping_2 := apiV1Mapping_1 + + roleList_3 := map[string]string{"admin": "admin", "user": "user"} + apiV1Mapping_3 := apiV1Mapping_1 + + roleList_4 := roleList_2 + apiV1Mapping_4 := map[string]map[string][]string{"/api/v1/unknown/serverinfo": {"GET": {"admin", "viewer"}}} + + roleList_5 := roleList_2 + apiV1Mapping_5 := map[string]map[string][]string{"/api/v1/spire/serverinfo": {"POST": {"admin", "viewer"}}} + + // fail when roles in apiMapping not in roleList + _, err = NewRBACAuthorizer(policyName, roleList_1, nil, apiV1Mapping_1) + expectedErr := "Could not parse policy testPolicy: invalid mapping: API V1 /api/v1/spire/serverinfo lists undefined role viewer" + if err == nil { + t.Fatal("ERROR: successfully initialized RBAC without roles") + } else if err.Error() != expectedErr { + t.Fatalf("ERROR: expected %s, got %s", expectedErr, err.Error()) + } + + // pass when roles in apiMapping in roleList + _, err = NewRBACAuthorizer(policyName, roleList_2, nil, apiV1Mapping_2) + if err != nil { + t.Fatalf("ERROR: failed to initialize RBAC: %s", err.Error()) + } + + // fail when typo in apiMapping + _, err = NewRBACAuthorizer(policyName, roleList_3, nil, apiV1Mapping_3) + if err == nil { + t.Fatalf("expected an error but got nil") + } + expectedPhrase := "undefined role viewer" + if !strings.Contains(err.Error(), expectedPhrase) { + t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) + } + + // fail when apiV1Mapping has path not in staticAPIV1List + _, err = NewRBACAuthorizer(policyName, roleList_4, nil, apiV1Mapping_4) + if err == nil { + t.Fatal("ERROR: successfully initialized RBAC without roles") + } + expectedPhrase = "/api/v1/unknown/serverinfo does not exist" + if !strings.Contains(err.Error(), expectedPhrase) { + t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) + } + + // fail when apiV1Mapping has method not in staticAPIV1List + _, err = NewRBACAuthorizer(policyName, roleList_5, nil, apiV1Mapping_5) + if err == nil { + t.Fatal("ERROR: successfully initialized RBAC without roles") + } + expectedPhrase = "does not exist with method POST" + if !strings.Contains(err.Error(), expectedPhrase) { + t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) + } + } +// func TestAuthorizeRequest(t *testing.T) { From 00227a8329b4c7243c36a4cde171c8962b592b97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:35:35 -0400 Subject: [PATCH 33/39] Bump dompurify from 3.0.8 to 3.1.6 in /frontend (#496) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.0.8 to 3.1.6. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.0.8...3.1.6) --- updated-dependencies: - dependency-name: dompurify dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c1eec82a..0abc976b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9122,9 +9122,9 @@ } }, "node_modules/dompurify": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.8.tgz", - "integrity": "sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ==" + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" }, "node_modules/domutils": { "version": "3.1.0", From 13fdcb0f7bd1709b081ec4a48af1055401e75571 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:11:52 -0400 Subject: [PATCH 34/39] Bump serve-static and express in /frontend (#497) Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `serve-static` from 1.15.0 to 1.16.2 - [Release notes](https://github.com/expressjs/serve-static/releases) - [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md) - [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: serve-static dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 187 +++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 70 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0abc976b..b045ffba 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6857,9 +6857,9 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6869,7 +6869,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -7071,13 +7071,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8852,16 +8857,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -9251,9 +9259,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -9465,6 +9473,25 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -10382,36 +10409,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -10436,9 +10463,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/extract-zip": { "version": "2.0.1", @@ -10681,12 +10708,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -11152,15 +11179,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11420,11 +11451,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15103,9 +15134,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -17709,11 +17743,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -19355,9 +19389,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -19390,6 +19424,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -19474,29 +19516,30 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -19570,13 +19613,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" From 731e448b230dc0a6d398285ef746ca02838a6802 Mon Sep 17 00:00:00 2001 From: MohammedAbdi Date: Tue, 10 Sep 2024 13:55:35 -0400 Subject: [PATCH 35/39] centralize api endpoints and update with versioned apis Signed-off-by: MohammedAbdi --- frontend/.env | 1 + frontend/src/components/apiConfig.ts | 18 + frontend/src/components/cluster-create.tsx | 1 + .../src/components/tornjak-api-helpers.tsx | 327 ++++++++++-------- frontend/src/env.ts | 1 + 5 files changed, 199 insertions(+), 149 deletions(-) create mode 100644 frontend/src/components/apiConfig.ts diff --git a/frontend/.env b/frontend/.env index b40176c3..761d8972 100644 --- a/frontend/.env +++ b/frontend/.env @@ -5,6 +5,7 @@ NODE_PATH=src/ SKIP_PREFLIGHT_CHECK=true REACT_APP_DEBUG_TORNJAK=true REACT_APP_TORNJAK_MANAGER=false +REACT_APP_API_VERSION=v1 ##### Backend Server uri REACT_APP_API_SERVER_URI=http://localhost:10000/ diff --git a/frontend/src/components/apiConfig.ts b/frontend/src/components/apiConfig.ts new file mode 100644 index 00000000..22d64724 --- /dev/null +++ b/frontend/src/components/apiConfig.ts @@ -0,0 +1,18 @@ +import {env} from '../env'; + +const API_VERSION = env.REACT_APP_API_VERSION; +const API_BASE_URL = `/api/${API_VERSION}`; +const apiEndpoints = { + spireServerInfoApi: `${API_BASE_URL}/spire/serverinfo`, + spireHealthCheckApi: `${API_BASE_URL}/spire/healthcheck`, + spireAgentsApi: `${API_BASE_URL}/spire/agents`, + spireAgentsBanApi: `${API_BASE_URL}/spire/agents/ban`, + spireJoinTokenApi: `${API_BASE_URL}/spire/agents/jointoken`, + spireEntriesApi: `${API_BASE_URL}/spire/entries`, + tornjakServerInfoApi: `${API_BASE_URL}/tornjak/serverinfo`, + tornjakSelectorsApi: `${API_BASE_URL}/tornjak/selectors`, + tornjakAgentsApi: `${API_BASE_URL}/tornjak/agents`, + tornjakClustersApi: `${API_BASE_URL}/tornjak/clusters`, +}; + +export default apiEndpoints; diff --git a/frontend/src/components/cluster-create.tsx b/frontend/src/components/cluster-create.tsx index 1b6d1b5f..43410aae 100644 --- a/frontend/src/components/cluster-create.tsx +++ b/frontend/src/components/cluster-create.tsx @@ -25,6 +25,7 @@ import { TornjakServerInfo } from './types' import { showResponseToast, showToast } from './error-api'; +// import apiEndpoints from './apiConfig'; type ClusterCreateProp = { // dispatches a payload for the server trust domain and nodeAttestorPlugin as a ServerInfoType and has a return type of void diff --git a/frontend/src/components/tornjak-api-helpers.tsx b/frontend/src/components/tornjak-api-helpers.tsx index df847ada..b4471713 100644 --- a/frontend/src/components/tornjak-api-helpers.tsx +++ b/frontend/src/components/tornjak-api-helpers.tsx @@ -18,6 +18,7 @@ import { showResponseToast } from './error-api'; // import { logError } from './helpers'; // import { displayResponseError } from './error-api'; import { env } from '../env'; +import apiEndpoints from './apiConfig'; const Auth_Server_Uri = env.REACT_APP_AUTH_SERVER_URI; type TornjakApiProp = {} @@ -61,7 +62,7 @@ class TornjakApi extends Component { spireHealthCheckingFunc: { (globalSpireHealthChecking: boolean): void; }, ) => { spireHealthCheckingFunc(false); - axios.get(GetApiServerUri("/api/healthcheck"), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.spireHealthCheckApi), { crossdomain: true }) .then(response => { console.log("SPIRE HEALTH:", response.data.status); if (response.data.status === 1) { @@ -77,56 +78,17 @@ class TornjakApi extends Component { }) } - registerSelectors = (serverName: string, wLoadAttdata: { spiffeid: string; plugin: string; }, - refreshSelectorsState: { (serverName: string, agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void): void; }, - agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void) => { - axios.post(GetApiServerUri('/manager-api/tornjak/selectors/register/') + serverName, wLoadAttdata) - .then(res => { - refreshSelectorsState(serverName, agentworkloadSelectorInfoFunc); - } - ) - .catch((error) => { - showResponseToast(error, { caption: "Could not register selectors." }) - }) - } - + // registerLocalSelectors registers the selected selectors in local mode registerLocalSelectors = (wLoadAttdata: { spiffeid: string; plugin: string; }, refreshLocalSelectorsState: { (agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void): void; }, agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void) => { - axios.post(GetApiServerUri('/api/tornjak/selectors/register'), wLoadAttdata) + axios.post(GetApiServerUri(apiEndpoints.tornjakSelectorsApi), wLoadAttdata) .then(res => refreshLocalSelectorsState(agentworkloadSelectorInfoFunc)) .catch((error) => { showResponseToast(error, { caption: "Could not register local selectors." }) }) } - // refreshSelectorsState returns the list agent's with their workload plugin info for the selected server in manager mode - // [ - // "agent1workloadselectorinfo": [ - // { - // "id": "agentid", - // "spiffeid": "agentspiffeeid", - // "selectors": "agentworkloadselectors" - // } - // ], - // "agent2workloadselectorinfo": [ - // { - // "id": "agentid", - // "spiffeid": "agentspiffeeid", - // "selectors": "agentworkloadselectors" - // } - // ] - // ] - refreshSelectorsState = (serverName: string, - agentworkloadSelectorInfoFunc: { - (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]): void; - (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]): void; - }) => { - axios.get(GetApiServerUri("/manager-api/tornjak/selectors/list/") + serverName, { crossdomain: true }) - .then(response => { - agentworkloadSelectorInfoFunc(response.data["agents"]); - }) - .catch((error) => showResponseToast(error, { caption: "Could not refresh selector state." })) - } + // refreshLocalSelectorsState returns the list agent's with their workload plugin info for the local server // [ // "agent1workloadselectorinfo": [ @@ -147,7 +109,7 @@ class TornjakApi extends Component { refreshLocalSelectorsState = (agentworkloadSelectorInfoFunc: { (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]): void; }) => { - axios.get(GetApiServerUri("/api/tornjak/selectors/list"), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.tornjakSelectorsApi), { crossdomain: true }) .then(response => { console.log(response.data) agentworkloadSelectorInfoFunc(response.data["agents"]) @@ -155,64 +117,24 @@ class TornjakApi extends Component { .catch((error) => showResponseToast(error, { caption: "Could not refresh local selector states." })) } - // populateTornjakAgentInfo returns tornjak info of requested agents including cluster name and selector - populateTornjakAgentInfo = (serverName: string, - agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void, - inputData: string) => { - axios.post(GetApiServerUri("/manager-api/tornjak/agents/list/") + serverName, inputData, - { - crossdomain: true, - }) - .then(response => { - agentworkloadSelectorInfoFunc(response.data["agents"]); - }) - .catch((error) => showResponseToast(error, { caption: "Could not populate tornjak agent info." })) - } - // populateLocalTornjakAgentInfo returns tornjak info of requested agents including cluster name and selector populateLocalTornjakAgentInfo = ( agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void, inputData: string ) => { - axios.post(GetApiServerUri("/api/tornjak/agents/list"), inputData, { crossdomain: true }) + axios.post(GetApiServerUri(apiEndpoints.tornjakAgentsApi), inputData, { crossdomain: true }) .then(response => { agentworkloadSelectorInfoFunc(response.data["agents"]) }) .catch((error) => showResponseToast(error, { caption: "Could not populate local tornjak agent info." })) } - // populateTornjakServerInfo returns the tornjak server info of the selected server in manager mode - populateTornjakServerInfo = ( - serverName: string, - tornjakServerInfoUpdateFunc: { (globalTornjakServerInfo: TornjakServerInfo): void }, - tornjakMessageFunc: { (globalErrorMessage: string): void } - ) => { - axios.get(GetApiServerUri('/manager-api/tornjak/serverinfo/') + serverName, { crossdomain: true }) - .then(response => { - tornjakServerInfoUpdateFunc(response.data) - tornjakMessageFunc(response.statusText) - }).catch(error => { - showResponseToast(error, { caption: "Could not populate tornjak server info." }) - tornjakServerInfoUpdateFunc({ - plugins: { - DataStore: [], - KeyManager: [], - NodeAttestor: [], - NodeResolver: [], - Notifier: [] - }, - trustDomain: "", - verboseConfig: "" - }); - }); - } - // populateLocalTornjakDebugServerInfo returns the debug server info of the server in local mode populateLocalTornjakDebugServerInfo = ( spireDebugServerInfoUpdateFunc: { (globalDebugServerInfo: DebugServerInfo): void }, tornjakMessageFunc: { (globalErrorMessage: string): void } ) => { - axios.get(GetApiServerUri('/api/debugserver'), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.spireServerInfoApi), { crossdomain: true }) .then(response => { spireDebugServerInfoUpdateFunc(response.data) tornjakMessageFunc(response.statusText) @@ -227,7 +149,7 @@ class TornjakApi extends Component { tornjakServerInfoUpdateFunc: { (globalTornjakServerInfo: TornjakServerInfo): void }, tornjakMessageFunc: { (globalErrorMessage: string): void } ) => { - axios.get(GetApiServerUri('/api/tornjak/serverinfo'), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.tornjakServerInfoApi), { crossdomain: true }) .then(response => { if (response.status === 200) { tornjakServerInfoUpdateFunc(response.data) @@ -266,25 +188,10 @@ class TornjakApi extends Component { serverInfoUpdateFunc(reqInfo); } - populateEntriesUpdate = (serverName: string, - entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void; }, - tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { - axios.get(GetApiServerUri('/manager-api/entry/list/') + serverName, { crossdomain: true }) - .then(response => { - if (!response.data["entries"]) { - entriesListUpdateFunc([]); - } else { entriesListUpdateFunc(response.data["entries"]); } - tornjakMessageFunc(response.statusText); - }).catch(error => { - entriesListUpdateFunc([]); - tornjakMessageFunc("Error retrieving " + serverName + " : " + error + (typeof (error.response) !== "undefined" ? ":" + error.response.data : "")); - showResponseToast(error, { caption: "Could not populate entries." }) - }) - } - + // populateLocalEntriesUpdate - returns the list of entries with their info in Local mode for the server populateLocalEntriesUpdate = (entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void; }, tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { - axios.get(GetApiServerUri('/api/entry/list'), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.spireEntriesApi), { crossdomain: true }) .then(response => { if (!response.data["entries"]) { entriesListUpdateFunc([]) @@ -299,30 +206,12 @@ class TornjakApi extends Component { }) } - // populateAgentsUpdate returns the list of agents with their info in manager mode for the selected server - populateAgentsUpdate = (serverName: string, - agentsListUpdateFunc: { (globalAgentsList: AgentsList[]): void; }, - tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { - axios.get(GetApiServerUri('/manager-api/agent/list/') + serverName, { crossdomain: true }) - .then(response => { - if (!response.data["agents"]) { - agentsListUpdateFunc([]); - } else { agentsListUpdateFunc(response.data["agents"]); } - tornjakMessageFunc(response.statusText); - }).catch(error => { - showResponseToast(error, { caption: "Could not populate agents." }) - agentsListUpdateFunc([]); - tornjakMessageFunc("Error retrieving " + serverName + " : " + error.message); - }); - - } - // populateLocalAgentsUpdate - returns the list of agents with their info in Local mode for the server populateLocalAgentsUpdate = (agentsListUpdateFunc: { (globalAgentsList: AgentsList[]): void; }, tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { - axios.get(GetApiServerUri('/api/agent/list'), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.spireAgentsApi), { crossdomain: true }) .then(response => { if (!response.data["agents"]) { agentsListUpdateFunc([]); @@ -336,27 +225,12 @@ class TornjakApi extends Component { }) } - // populateClustersUpdate returns the list of clusters with their info in manager mode for the selected server - populateClustersUpdate = (serverName: string, - clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void; }, - tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { - axios.get(GetApiServerUri('/manager-api/tornjak/clusters/list/') + serverName, { crossdomain: true }) - .then(response => { - clustersListUpdateFunc(response.data["clusters"]); - tornjakMessageFunc(response.statusText); - }).catch(error => { - showResponseToast(error, { caption: "Could not populate clusters." }) - clustersListUpdateFunc([]); - tornjakMessageFunc("Error retrieving " + serverName + " : " + error.message); - }); - } - // populateLocalClustersUpdate - returns the list of clusters with their info in Local mode for the server populateLocalClustersUpdate = ( clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, tornjakMessageFunc: { (globalErrorMessage: string): void } ) => { - axios.get(GetApiServerUri('/api/tornjak/clusters/list'), { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.tornjakClustersApi), { crossdomain: true }) .then(response => { clustersListUpdateFunc(response.data["clusters"]) tornjakMessageFunc(response.statusText) @@ -368,6 +242,46 @@ class TornjakApi extends Component { }) } + // localClusterDelete - returns success message after successful deletion of a cluster in Local mode for the server + + async localClusterDelete( + inputData: { cluster: { name: string } }, + clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, + globalClustersList: any[] + ) { + try { + const response = await axios.delete(GetApiServerUri(apiEndpoints.tornjakClustersApi), { + data: inputData, + headers: { + 'Content-Type': 'application/json' + }, + crossdomain: true, + }); + + clustersListUpdateFunc(globalClustersList.filter(el => el.name !== inputData.cluster.name)); + return response.data; + } catch (error) { + return error; + } + } + // async localClusterDelete(inputData: { cluster: { name: string; }; }, clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, globalClustersList: any[]) { + // const response = await axios.post(GetApiServerUri(apiEndpoints.tornjakClustersApi), inputData, + // { + // crossdomain: true, + // }) + // .then(function (response) { + // clustersListUpdateFunc(globalClustersList.filter(el => + // el.name !== inputData)) + // return response.data; + // }) + // .catch(function (error) { + // return error.message; + // }) + // return response; + // } + + // manager apis + // clusterDelete - returns success message after successful deletion of a cluster in manager mode async clusterDelete(serverName: string, inputData: { cluster: { name: string; }; }, clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, globalClustersList: any[]) { const response = await axios.post(GetApiServerUri("/manager-api/tornjak/clusters/delete/") + serverName, inputData, @@ -385,21 +299,136 @@ class TornjakApi extends Component { return response.data; } - // localClusterDelete - returns success message after successful deletion of a cluster in Local mode for the server - async localClusterDelete(inputData: { cluster: { name: string; }; }, clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, globalClustersList: any[]) { - const response = await axios.post(GetApiServerUri("/api/tornjak/clusters/delete"), inputData, + // populateClustersUpdate returns the list of clusters with their info in manager mode for the selected server + populateClustersUpdate = (serverName: string, + clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void; }, + tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { + axios.get(GetApiServerUri('/manager-api/tornjak/clusters/list/') + serverName, { crossdomain: true }) + .then(response => { + clustersListUpdateFunc(response.data["clusters"]); + tornjakMessageFunc(response.statusText); + }).catch(error => { + showResponseToast(error, { caption: "Could not populate clusters." }) + clustersListUpdateFunc([]); + tornjakMessageFunc("Error retrieving " + serverName + " : " + error.message); + }); + } + + // populateAgentsUpdate returns the list of agents with their info in manager mode for the selected server + populateAgentsUpdate = (serverName: string, + agentsListUpdateFunc: { (globalAgentsList: AgentsList[]): void; }, + tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { + axios.get(GetApiServerUri('/manager-api/agent/list/') + serverName, { crossdomain: true }) + .then(response => { + if (!response.data["agents"]) { + agentsListUpdateFunc([]); + } else { agentsListUpdateFunc(response.data["agents"]); } + tornjakMessageFunc(response.statusText); + }).catch(error => { + showResponseToast(error, { caption: "Could not populate agents." }) + agentsListUpdateFunc([]); + tornjakMessageFunc("Error retrieving " + serverName + " : " + error.message); + }); + } + + // populateEntriesUpdate - returns the list of entries with their info in manager mode for the server + populateEntriesUpdate = (serverName: string, + entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void; }, + tornjakMessageFunc: { (globalErrorMessage: string): void; }) => { + axios.get(GetApiServerUri('/manager-api/entry/list/') + serverName, { crossdomain: true }) + .then(response => { + if (!response.data["entries"]) { + entriesListUpdateFunc([]); + } else { entriesListUpdateFunc(response.data["entries"]); } + tornjakMessageFunc(response.statusText); + }).catch(error => { + entriesListUpdateFunc([]); + tornjakMessageFunc("Error retrieving " + serverName + " : " + error + (typeof (error.response) !== "undefined" ? ":" + error.response.data : "")); + showResponseToast(error, { caption: "Could not populate entries." }) + }) + } + + // populateTornjakServerInfo returns the tornjak server info of the selected server in manager mode + populateTornjakServerInfo = ( + serverName: string, + tornjakServerInfoUpdateFunc: { (globalTornjakServerInfo: TornjakServerInfo): void }, + tornjakMessageFunc: { (globalErrorMessage: string): void } + ) => { + axios.get(GetApiServerUri('/manager-api/tornjak/serverinfo/') + serverName, { crossdomain: true }) + .then(response => { + tornjakServerInfoUpdateFunc(response.data) + tornjakMessageFunc(response.statusText) + }).catch(error => { + showResponseToast(error, { caption: "Could not populate tornjak server info." }) + tornjakServerInfoUpdateFunc({ + plugins: { + DataStore: [], + KeyManager: [], + NodeAttestor: [], + NodeResolver: [], + Notifier: [] + }, + trustDomain: "", + verboseConfig: "" + }); + }); + } + + // populateTornjakAgentInfo returns tornjak info of requested agents including cluster name and selector + populateTornjakAgentInfo = (serverName: string, + agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void, + inputData: string) => { + axios.post(GetApiServerUri("/manager-api/tornjak/agents/list/") + serverName, inputData, { crossdomain: true, }) - .then(function (response) { - clustersListUpdateFunc(globalClustersList.filter(el => - el.name !== inputData)) - return response.data; + .then(response => { + agentworkloadSelectorInfoFunc(response.data["agents"]); }) - .catch(function (error) { - return error.message; + .catch((error) => showResponseToast(error, { caption: "Could not populate tornjak agent info." })) + } + + // refreshSelectorsState returns the list agent's with their workload plugin info for the selected server in manager mode + // [ + // "agent1workloadselectorinfo": [ + // { + // "id": "agentid", + // "spiffeid": "agentspiffeeid", + // "selectors": "agentworkloadselectors" + // } + // ], + // "agent2workloadselectorinfo": [ + // { + // "id": "agentid", + // "spiffeid": "agentspiffeeid", + // "selectors": "agentworkloadselectors" + // } + // ] + // ] + refreshSelectorsState = (serverName: string, + agentworkloadSelectorInfoFunc: { + (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]): void; + (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]): void; + }) => { + axios.get(GetApiServerUri("/manager-api/tornjak/selectors/list/") + serverName, { crossdomain: true }) + .then(response => { + agentworkloadSelectorInfoFunc(response.data["agents"]); + }) + .catch((error) => showResponseToast(error, { caption: "Could not refresh selector state." })) + } + + // registerSelectors registers the selected selectors in manager mode + registerSelectors = (serverName: string, wLoadAttdata: { spiffeid: string; plugin: string; }, + refreshSelectorsState: { (serverName: string, agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void): void; }, + agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void) => { + axios.post(GetApiServerUri('/manager-api/tornjak/selectors/register/') + serverName, wLoadAttdata) + .then(res => { + refreshSelectorsState(serverName, agentworkloadSelectorInfoFunc); + } + ) + .catch((error) => { + showResponseToast(error, { caption: "Could not register selectors." }) }) - return response; } } diff --git a/frontend/src/env.ts b/frontend/src/env.ts index 3e12aa81..2e414d2d 100644 --- a/frontend/src/env.ts +++ b/frontend/src/env.ts @@ -10,5 +10,6 @@ declare global { REACT_APP_TORNJAK_MANAGER: string, REACT_APP_KEYCLOAK_REALM: string, REACT_APP_OIDC_CLIENT_ID: string, + REACT_APP_API_VERSION: string, } export const env: EnvType = { ...process.env, ...window.env } \ No newline at end of file From 0fdffcaabacb3d42591af488fbce9dcc38d51672 Mon Sep 17 00:00:00 2001 From: MohammedAbdi Date: Wed, 11 Sep 2024 10:18:44 -0400 Subject: [PATCH 36/39] update delete and post apis Signed-off-by: MohammedAbdi --- frontend/src/components/agent-create-join-token.tsx | 3 ++- frontend/src/components/cluster-create.tsx | 4 ++-- frontend/src/components/cluster-edit.tsx | 3 ++- frontend/src/components/entry-create.tsx | 3 ++- frontend/src/tables/agents-list-table.tsx | 5 +++-- frontend/src/tables/clusters-list-table.tsx | 3 ++- frontend/src/tables/entries-list-table.tsx | 3 ++- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/agent-create-join-token.tsx b/frontend/src/components/agent-create-join-token.tsx index 45b02de7..65859249 100644 --- a/frontend/src/components/agent-create-join-token.tsx +++ b/frontend/src/components/agent-create-join-token.tsx @@ -7,6 +7,7 @@ import { serverSelectedFunc } from 'redux/actions'; import { RootState } from 'redux/reducers'; import { ToastContainer } from "react-toastify" import { showResponseToast, showToast } from './error-api'; +import apiEndpoints from './apiConfig'; type CreateJoinTokenProp = { globalServerSelected: string, @@ -102,7 +103,7 @@ class CreateJoinToken extends Component { getApiEntryCreateEndpoint(): string { if (!IsManager) { - return GetApiServerUri('/api/tornjak/clusters/create') + return GetApiServerUri(apiEndpoints.tornjakClustersApi) } else if (IsManager && this.state.selectedServer !== "") { return GetApiServerUri('/manager-api/tornjak/clusters/create') + "/" + this.state.selectedServer } else { diff --git a/frontend/src/components/cluster-edit.tsx b/frontend/src/components/cluster-edit.tsx index d04c1f96..15f34561 100644 --- a/frontend/src/components/cluster-edit.tsx +++ b/frontend/src/components/cluster-edit.tsx @@ -27,6 +27,7 @@ import { DebugServerInfo, } from './types'; import { showResponseToast, showToast } from './error-api'; +import apiEndpoints from './apiConfig'; type ClusterEditProp = { // tornjak server debug info of the selected server @@ -259,7 +260,7 @@ class ClusterEdit extends Component { getApiEntryCreateEndpoint(): string { if (!IsManager) { - return GetApiServerUri('/api/tornjak/clusters/edit') + return GetApiServerUri(apiEndpoints.tornjakClustersApi) } if (IsManager && this.state.selectedServer !== "") { return GetApiServerUri('/manager-api/tornjak/clusters/edit') + "/" + this.state.selectedServer diff --git a/frontend/src/components/entry-create.tsx b/frontend/src/components/entry-create.tsx index 472f6951..5870eb3b 100644 --- a/frontend/src/components/entry-create.tsx +++ b/frontend/src/components/entry-create.tsx @@ -47,6 +47,7 @@ import EntryExpiryFeatures from './entry-expiry-features'; import CreateEntryJson from './entry-create-json'; import { ToastContainer } from "react-toastify" import { showResponseToast, showToast } from './error-api'; +import apiEndpoints from './apiConfig'; // import PropTypes from "prop-types"; // needed for testing will be removed on last pr type CreateEntryProp = { @@ -573,7 +574,7 @@ class CreateEntry extends Component { getApiEntryCreateEndpoint(): string { if (!IsManager) { - return GetApiServerUri('/api/entry/create') + return GetApiServerUri(apiEndpoints.spireEntriesApi) } if (IsManager && this.state.selectedServer !== "") { return GetApiServerUri('/manager-api/entry/create') + "/" + this.state.selectedServer diff --git a/frontend/src/tables/agents-list-table.tsx b/frontend/src/tables/agents-list-table.tsx index f193154b..5d444073 100644 --- a/frontend/src/tables/agents-list-table.tsx +++ b/frontend/src/tables/agents-list-table.tsx @@ -11,6 +11,7 @@ import { AgentsList, AgentsWorkLoadAttestorInfo } from "components/types"; import { DenormalizedRow } from "carbon-components-react"; import { RootState } from "redux/reducers"; import { showResponseToast } from "components/error-api"; +import apiEndpoints from 'components/apiConfig'; // AgentListTable takes in // listTableData: agents data to be rendered on table @@ -99,7 +100,7 @@ class AgentsListTable extends React.Component Date: Wed, 11 Sep 2024 14:59:13 -0400 Subject: [PATCH 37/39] put 2 old apis back for testing Signed-off-by: MohammedAbdi --- frontend/src/components/cluster-edit.tsx | 4 ++-- frontend/src/tables/clusters-list-table.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/cluster-edit.tsx b/frontend/src/components/cluster-edit.tsx index 15f34561..1fdb8629 100644 --- a/frontend/src/components/cluster-edit.tsx +++ b/frontend/src/components/cluster-edit.tsx @@ -27,7 +27,7 @@ import { DebugServerInfo, } from './types'; import { showResponseToast, showToast } from './error-api'; -import apiEndpoints from './apiConfig'; +// import apiEndpoints from './apiConfig'; type ClusterEditProp = { // tornjak server debug info of the selected server @@ -260,7 +260,7 @@ class ClusterEdit extends Component { getApiEntryCreateEndpoint(): string { if (!IsManager) { - return GetApiServerUri(apiEndpoints.tornjakClustersApi) + return GetApiServerUri('/api/tornjak/clusters/edit') } if (IsManager && this.state.selectedServer !== "") { return GetApiServerUri('/manager-api/tornjak/clusters/edit') + "/" + this.state.selectedServer diff --git a/frontend/src/tables/clusters-list-table.tsx b/frontend/src/tables/clusters-list-table.tsx index e9351484..0b243d52 100644 --- a/frontend/src/tables/clusters-list-table.tsx +++ b/frontend/src/tables/clusters-list-table.tsx @@ -11,7 +11,7 @@ import { ClustersList } from "components/types"; import { DenormalizedRow } from "carbon-components-react"; import { RootState } from "redux/reducers"; import { showResponseToast } from "components/error-api"; -import apiEndpoints from 'components/apiConfig'; +// import apiEndpoints from 'components/apiConfig'; // ClusterListTable takes in // listTableData: clusters data to be rendered on table @@ -96,7 +96,7 @@ class ClustersListTable extends React.Component Date: Thu, 12 Sep 2024 14:18:34 -0400 Subject: [PATCH 38/39] update api delete functions Signed-off-by: MohammedAbdi --- frontend/src/components/cluster-edit.tsx | 6 +- .../src/components/tornjak-api-helpers.tsx | 54 +++++++++++----- frontend/src/tables/agents-list-table.tsx | 31 +++++---- frontend/src/tables/clusters-list-table.tsx | 43 ++++++------- frontend/src/tables/entries-list-table.tsx | 64 ++++++++++--------- 5 files changed, 115 insertions(+), 83 deletions(-) diff --git a/frontend/src/components/cluster-edit.tsx b/frontend/src/components/cluster-edit.tsx index 1fdb8629..942037d2 100644 --- a/frontend/src/components/cluster-edit.tsx +++ b/frontend/src/components/cluster-edit.tsx @@ -27,7 +27,7 @@ import { DebugServerInfo, } from './types'; import { showResponseToast, showToast } from './error-api'; -// import apiEndpoints from './apiConfig'; +import apiEndpoints from './apiConfig'; type ClusterEditProp = { // tornjak server debug info of the selected server @@ -260,7 +260,7 @@ class ClusterEdit extends Component { getApiEntryCreateEndpoint(): string { if (!IsManager) { - return GetApiServerUri('/api/tornjak/clusters/edit') + return GetApiServerUri(apiEndpoints.tornjakClustersApi) } if (IsManager && this.state.selectedServer !== "") { return GetApiServerUri('/manager-api/tornjak/clusters/edit') + "/" + this.state.selectedServer @@ -343,7 +343,7 @@ class ClusterEdit extends Component { return } - axios.post(endpoint, cjtData) + axios.patch(endpoint, cjtData) .then( res => this.setState({ message: "Request:" + JSON.stringify(cjtData, null, ' ') + "\n\nSuccess:" + JSON.stringify(res.data, null, ' '), diff --git a/frontend/src/components/tornjak-api-helpers.tsx b/frontend/src/components/tornjak-api-helpers.tsx index b4471713..2673d7e6 100644 --- a/frontend/src/components/tornjak-api-helpers.tsx +++ b/frontend/src/components/tornjak-api-helpers.tsx @@ -257,31 +257,53 @@ class TornjakApi extends Component { }, crossdomain: true, }); - clustersListUpdateFunc(globalClustersList.filter(el => el.name !== inputData.cluster.name)); return response.data; } catch (error) { return error; } } - // async localClusterDelete(inputData: { cluster: { name: string; }; }, clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, globalClustersList: any[]) { - // const response = await axios.post(GetApiServerUri(apiEndpoints.tornjakClustersApi), inputData, - // { - // crossdomain: true, - // }) - // .then(function (response) { - // clustersListUpdateFunc(globalClustersList.filter(el => - // el.name !== inputData)) - // return response.data; - // }) - // .catch(function (error) { - // return error.message; - // }) - // return response; - // } + + // localEntryDelete - returns success message after successful deletion of a entry in Local mode for the server + async localEntryDelete( + inputData: { ids: string[] }, + entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void }, + globalEntriesList: EntriesList[] + ) { + try { + const response = await axios.delete(GetApiServerUri(apiEndpoints.spireEntriesApi), { + data: { ids: inputData.ids }, + headers: { + 'Content-Type': 'application/json' + }, + crossdomain: true, + }); + entriesListUpdateFunc(globalEntriesList.filter(el => !inputData.ids.includes(el.id))); + return response.data; + } catch (error) { + return error; + } + } // manager apis + // entryDelete - returns success message after successful deletion of a entry in manager mode + async entryDelete(serverName: string, inputData:{ ids: string[] }, entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void }, globalEntriesList: any[]) { + const response = await axios.post(GetApiServerUri("/manager-api/tornjak/entry/delete/") + serverName, inputData, + { + crossdomain: true, + }) + .then(function (response) { + entriesListUpdateFunc(globalEntriesList.filter(el => + el.name !== inputData)) + return response.data; + }) + .catch(function (error) { + return error.message; + }) + return response.data; + } + // clusterDelete - returns success message after successful deletion of a cluster in manager mode async clusterDelete(serverName: string, inputData: { cluster: { name: string; }; }, clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void }, globalClustersList: any[]) { const response = await axios.post(GetApiServerUri("/manager-api/tornjak/clusters/delete/") + serverName, inputData, diff --git a/frontend/src/tables/agents-list-table.tsx b/frontend/src/tables/agents-list-table.tsx index 5d444073..e3fb7ad1 100644 --- a/frontend/src/tables/agents-list-table.tsx +++ b/frontend/src/tables/agents-list-table.tsx @@ -108,15 +108,21 @@ class AgentsListTable extends React.Component { @@ -128,12 +134,13 @@ class AgentsListTable extends React.Component showResponseToast(error, {caption: "Could not delete agent."})) + .catch((error) => showResponseToast(error, { caption: "Could not delete agent." })) } banAgent(selectedRows: readonly DenormalizedRow[]) { - var id: {path: string; trust_domain: string}[] = [], i = 0, endpoint = "", prefix = "spiffe://" + var id: { path: string; trust_domain: string }[] = [], i = 0, endpoint = "", prefix = "spiffe://" if (IsManager) { endpoint = GetApiServerUri('/manager-api/agent/ban') + "/" + this.props.globalServerSelected @@ -145,16 +152,16 @@ class AgentsListTable extends React.Component { alert("Ban SUCCESS") this.componentDidMount() }) - .catch((error) => showResponseToast(error, {caption: "Could not ban agent."})) + .catch((error) => showResponseToast(error, { caption: "Could not ban agent." })) } } render() { diff --git a/frontend/src/tables/clusters-list-table.tsx b/frontend/src/tables/clusters-list-table.tsx index 0b243d52..276fe130 100644 --- a/frontend/src/tables/clusters-list-table.tsx +++ b/frontend/src/tables/clusters-list-table.tsx @@ -11,7 +11,7 @@ import { ClustersList } from "components/types"; import { DenormalizedRow } from "carbon-components-react"; import { RootState } from "redux/reducers"; import { showResponseToast } from "components/error-api"; -// import apiEndpoints from 'components/apiConfig'; +import TornjakApi from 'components/tornjak-api-helpers'; // ClusterListTable takes in // listTableData: clusters data to be rendered on table @@ -45,8 +45,10 @@ type ClustersListTableState = { } class ClustersListTable extends React.Component { + TornjakApi: TornjakApi; constructor(props: ClustersListTableProp) { super(props); + this.TornjakApi = new TornjakApi(props); this.state = { listData: props.data, listTableData: [], @@ -89,34 +91,29 @@ class ClustersListTable extends React.Component { - if (this.props.globalClustersList === undefined) return - for (let i = 0; i < responses.length; i++) { - this.props.clustersListUpdateFunc(this.props.globalClustersList.filter(el =>el.name !== cluster[i].name)) + cluster[i] = { name: selectedRows[i].cells[1].value }; + if (IsManager) { + successMessage = this.TornjakApi.clusterDelete(this.props.globalServerSelected, { cluster: cluster[i] }, this.props.clustersListUpdateFunc, this.props.globalClustersList); + } else { + successMessage = this.TornjakApi.localClusterDelete({ cluster: cluster[i] }, this.props.clustersListUpdateFunc, this.props.globalClustersList); + } + successMessage.then(function (result) { + if (result === "SUCCESS") { + window.alert(`CLUSTER "${cluster[i].name}" DELETED SUCCESSFULLY!`); + window.location.reload(); + } else { + window.alert(`Error deleting cluster "${cluster[i].name}": ` + result); } + return; }) - .catch((error) => showResponseToast(error, {caption: "Could not delete cluster."})) + } } + render() { const { listTableData } = this.state; const headerData = [ diff --git a/frontend/src/tables/entries-list-table.tsx b/frontend/src/tables/entries-list-table.tsx index a3d985a4..1dd33e83 100644 --- a/frontend/src/tables/entries-list-table.tsx +++ b/frontend/src/tables/entries-list-table.tsx @@ -13,6 +13,7 @@ import { DenormalizedRow } from "carbon-components-react"; import { saveAs } from "file-saver"; import { showResponseToast } from "components/error-api"; import apiEndpoints from 'components/apiConfig'; +import TornjakApi from 'components/tornjak-api-helpers'; // EntriesListTable takes in // listTableData: entries data to be rendered on table @@ -39,8 +40,10 @@ type EntriesListTableState = { } class EntriesListTable extends React.Component { + TornjakApi: TornjakApi; constructor(props: EntriesListTableProp) { super(props); + this.TornjakApi = new TornjakApi(props); this.state = { listData: props.data, listTableData: [{ "id": "0" }], @@ -84,33 +87,36 @@ class EntriesListTable extends React.Component { - if (this.props.globalEntriesList === undefined) { - return - } - for (let i = 0; i < responses.length; i++) { - this.props.entriesListUpdateFunc(this.props.globalEntriesList.filter(el => el.id !== responses[i].data.results[0].id)) + if (!selectedRows || selectedRows.length === 0) return ""; + + // Collect the IDs of the selected entries + const idsToDelete = selectedRows.map(row => row.cells[1].value); + + const deletePromise = IsManager + ? this.TornjakApi.entryDelete(this.props.globalServerSelected, { ids: idsToDelete }, this.props.entriesListUpdateFunc, this.props.globalEntriesList) + : this.TornjakApi.localEntryDelete({ ids: idsToDelete }, this.props.entriesListUpdateFunc, this.props.globalEntriesList); + + deletePromise + .then(response => { + const results = response.results; // Ensure you're accessing the 'results' array in the response + + if (Array.isArray(results)) { + const successIds = results.map(result => result.id); + const failedIds = idsToDelete.filter(id => !successIds.includes(id)); + + if (failedIds.length === 0) { + window.alert(`Entries deleted successfully!`); + window.location.reload(); // Reload the page or update the UI as needed + } else { + window.alert(`Error deleting entries with IDs: ${failedIds.join(', ')}`); + } + } else { + window.alert("Unexpected response format. Could not delete entries."); } }) - .catch((error) => showResponseToast(error, {caption: "Could not delete entry."})) + .catch(error => { + window.alert(`Error deleting entries: ${error.message}`); + }); } downloadEntries(selectedRows: readonly DenormalizedRow[]) { @@ -118,14 +124,14 @@ class EntriesListTable extends React.Component Date: Mon, 16 Sep 2024 13:27:31 -0400 Subject: [PATCH 39/39] change post to get Signed-off-by: MohammedAbdi --- frontend/src/components/tornjak-api-helpers.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/tornjak-api-helpers.tsx b/frontend/src/components/tornjak-api-helpers.tsx index 2673d7e6..5a1d4f64 100644 --- a/frontend/src/components/tornjak-api-helpers.tsx +++ b/frontend/src/components/tornjak-api-helpers.tsx @@ -120,13 +120,16 @@ class TornjakApi extends Component { // populateLocalTornjakAgentInfo returns tornjak info of requested agents including cluster name and selector populateLocalTornjakAgentInfo = ( agentworkloadSelectorInfoFunc: (globalAgentsWorkLoadAttestorInfo: AgentsWorkLoadAttestorInfo[]) => void, - inputData: string + inputData: string // Assuming this input data is used as query parameters ) => { - axios.post(GetApiServerUri(apiEndpoints.tornjakAgentsApi), inputData, { crossdomain: true }) + axios.get(GetApiServerUri(apiEndpoints.tornjakAgentsApi), { + params: { input: inputData }, // Add inputData as query parameter + crossdomain: true + }) .then(response => { - agentworkloadSelectorInfoFunc(response.data["agents"]) + agentworkloadSelectorInfoFunc(response.data["agents"]); }) - .catch((error) => showResponseToast(error, { caption: "Could not populate local tornjak agent info." })) + .catch((error) => showResponseToast(error, { caption: "Could not populate local tornjak agent info." })); } // populateLocalTornjakDebugServerInfo returns the debug server info of the server in local mode @@ -251,7 +254,7 @@ class TornjakApi extends Component { ) { try { const response = await axios.delete(GetApiServerUri(apiEndpoints.tornjakClustersApi), { - data: inputData, + data: inputData, headers: { 'Content-Type': 'application/json' }, @@ -272,7 +275,7 @@ class TornjakApi extends Component { ) { try { const response = await axios.delete(GetApiServerUri(apiEndpoints.spireEntriesApi), { - data: { ids: inputData.ids }, + data: { ids: inputData.ids }, headers: { 'Content-Type': 'application/json' }, @@ -288,7 +291,7 @@ class TornjakApi extends Component { // manager apis // entryDelete - returns success message after successful deletion of a entry in manager mode - async entryDelete(serverName: string, inputData:{ ids: string[] }, entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void }, globalEntriesList: any[]) { + async entryDelete(serverName: string, inputData: { ids: string[] }, entriesListUpdateFunc: { (globalEntriesList: EntriesList[]): void }, globalEntriesList: any[]) { const response = await axios.post(GetApiServerUri("/manager-api/tornjak/entry/delete/") + serverName, inputData, { crossdomain: true,