├── .github ├── actions │ └── build │ │ └── action.yaml ├── compute-version │ └── action.yaml ├── dependabot.yaml └── workflows │ ├── .ci.yaml.swp │ ├── ci.yaml │ ├── linting.yaml │ └── master-build.yaml ├── .gitignore ├── .markdownlint.yml ├── CODE-OF-CONDUCT.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Dockerfile.backend-container ├── Dockerfile.backend-container.ubi ├── Dockerfile.tornjak-manager ├── LICENSE ├── Makefile ├── README.md ├── USAGE.md ├── api ├── agent │ ├── config.go │ ├── crd_handlers.go │ ├── handlers.go │ ├── server.go │ ├── spire_apis.go │ ├── tornjak_apis.go │ └── types.go └── manager │ ├── api.go │ └── server.go ├── cmd ├── agent │ └── main.go └── manager │ └── main.go ├── docs ├── README.md ├── architecture-diagrams │ ├── Tornjak Agent Mode Architecture.png │ ├── Tornjak Architecture with IAM Feb 2023.pptx │ └── Tornjak Manager Mode Architecture.png ├── auth │ └── keycloak │ │ ├── diagrams │ │ ├── browser-flow.png │ │ ├── github-keycloak.png │ │ ├── github-oauth-app-secret.png │ │ ├── github-oauth-app.png │ │ ├── google-cloud-console.png │ │ ├── google-cloud-credentials.png │ │ ├── google-keycloak.png │ │ ├── identity-providers-homepage.png │ │ ├── keycloak-sign-in-page.png │ │ ├── microsoft-azure-overview.png │ │ ├── microsoft-azure-secret.png │ │ ├── microsoft-azure.png │ │ ├── microsoft-keycloak.png │ │ └── upstream-architecture-diagram.png │ │ ├── keycloak-configuration.md │ │ └── keycloak-upstream-IAMs-conf.md ├── blogs.md ├── conf │ └── agent │ │ ├── base.conf │ │ └── full.conf ├── config-tornjak-server.md ├── helm │ ├── tornjak-helm-chart.md │ └── values.yaml ├── newEntry-json-format.md ├── newFederation-json-format.md ├── plan.md ├── plugins │ ├── plugin_server_authentication_keycloak.md │ ├── plugin_server_authorization_rbac.md │ ├── plugin_server_datastore_sql.md │ └── plugin_server_spirecrd.md ├── quickstart │ ├── README.md │ ├── agent-account.yaml │ ├── agent-cluster-role.yaml │ ├── agent-configmap.yaml │ ├── agent-daemonset.yaml │ ├── client-deployment.yaml │ ├── create-node-registration-entry.sh │ ├── kustomization.yaml │ ├── server-account.yaml │ ├── server-cluster-role.yaml │ ├── server-configmap.yaml │ ├── server-service.yaml │ ├── server-statefulset-examples │ │ ├── backend-sidecar-server-statefulset.yaml │ │ └── tornjak-sidecar-server-statefulset.yaml │ ├── server-statefulset.yaml │ ├── spire-bundle-configmap.yaml │ ├── spire-namespace.yaml │ ├── test.sh │ └── tornjak-configmap.yaml ├── rsrc │ ├── agent-extended.png │ ├── agent.png │ ├── keycloak_diagrams │ │ ├── 10-realm-import-page.png │ │ ├── 11-imported-realm-json.png │ │ ├── 12-imported-resources-window.png │ │ ├── 13-add-redirect-uri.png │ │ ├── 14-invalid-redirect-uri.png │ │ ├── 15-ingress-for-redirect-uri.png │ │ ├── 16-login-page.png │ │ ├── 17-import-client-page.png │ │ ├── 18-imported-client-page.png │ │ ├── 19-create-client-page.png │ │ ├── 20-client-general.png │ │ ├── 21-client-capability-config.png │ │ ├── 22-client-settings-page.png │ │ ├── 23-client-access-settings.png │ │ ├── 24-client-scopes-page.png │ │ ├── 25-full-scope-allowed.png │ │ ├── 26-realm-roles-page.png │ │ ├── 27-realm-create-role.png │ │ ├── 28-realm-created-roles.png │ │ ├── 29-client-roles.png │ │ ├── 30-client-created-roles.png │ │ ├── 31-client-associated-roles.png │ │ ├── 32-list-of-available-realm-roles.png │ │ ├── 33-create-group.png │ │ ├── 34-role-mapping.png │ │ ├── 35-created-groups.png │ │ ├── 36-realm-settings.png │ │ ├── 37-user-registration.png │ │ ├── 38-create-user.png │ │ ├── 39-select-group.png │ │ ├── 4-server-access-ui.jpg │ │ ├── 40-created-users.png │ │ ├── 41-user-credentials.png │ │ ├── 42-assign-password.png │ │ ├── 5-server-login-page.png │ │ ├── 6-admin-console.png │ │ ├── 7-create-realm-page.png │ │ ├── 8-realm-page.png │ │ └── 9-realm-settings-page.png │ ├── mac-system-network.png │ ├── mac-system-renewDHCP.png │ ├── mac-system-wifi.png │ ├── manager.png │ ├── pkce-auth-code-flow.png │ ├── standard-auth-code-flow.png │ ├── tornjak-agent-browser.png │ ├── tornjak-agent-list.png │ ├── tornjak-backend-diagram.png │ ├── tornjak-backend-plugin-diagram.png │ ├── tornjak-create-entries.png │ ├── tornjak-create-token.png │ ├── tornjak-entries-list.png │ ├── tornjak-manage-servers.png │ ├── tornjak-serverInfo.png │ ├── tornjak-ui-diagram.png │ ├── tornjak-ui.png │ └── vsdx diagrams │ │ ├── Tornjak UI API Diagram-Agent list.vsdx │ │ ├── Tornjak UI API Diagram-Create Entries.vsdx │ │ ├── Tornjak UI API Diagram-Create Token.vsdx │ │ ├── Tornjak UI API Diagram-Entries List.vsdx │ │ ├── Tornjak UI API Diagram-High Level Back End Box Diagram.vsdx │ │ ├── Tornjak UI API Diagram-High Level UI Box Diagram.vsdx │ │ ├── Tornjak UI API Diagram-Manage Servers.vsdx │ │ └── Tornjak UI API Diagram-Tornjak ServerInfo.vsdx ├── tornjak-agent.md ├── tornjak-hints.md ├── tornjak-manager.md ├── tornjak-ui-api-documentation.md └── user-management.md ├── examples ├── docker-compose │ ├── Dockerfile.add-keycloak │ ├── docker-compose-frontend.yml │ ├── docker-compose-keycloak.yml │ └── frontend.yml ├── keycloak │ ├── README.md │ ├── client │ │ └── tornjak-keycloak-client-import.json │ ├── config.yaml │ ├── realm │ │ └── tornjak-realm.json │ ├── service.yaml │ └── statefulset.yaml ├── multiple_entries_template.json ├── registrar_template.json └── tls_mtls │ ├── CA-client │ ├── rootCA.crt │ ├── rootCA.key │ └── rootCA.srl │ ├── CA-server │ ├── rootCA.crt │ ├── rootCA.key │ └── rootCA.srl │ ├── README.md │ ├── client.crt │ ├── client.csr │ ├── client.key │ ├── create-ca.sh │ ├── create-cert.sh │ ├── server-statefulset-mtls.yaml │ ├── server-statefulset-tls.yaml │ ├── server.crt │ ├── server.csr │ ├── server.key │ └── tornjak-configmap.yaml ├── frontend ├── .env ├── .env.prod ├── .env.staging ├── .npmrc ├── Dockerfile.frontend-container ├── Dockerfile.frontend-container.ubi ├── axios.d.ts ├── babel.config.json ├── jest.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.tsx │ ├── Utils │ │ └── index.js │ ├── auth │ │ └── KeycloakAuth.js │ ├── charts │ │ └── PieChart.tsx │ ├── components │ │ ├── AccessNotAllowed.tsx │ │ ├── NavDropdown.tsx │ │ ├── RenderOnAdminRole.tsx │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ ├── cluster-create.test.jsx.snap │ │ │ │ └── cluster-list.test.jsx.snap │ │ │ ├── cluster-create.test.jsx │ │ │ └── cluster-list.test.jsx │ │ ├── agent-create-join-token.tsx │ │ ├── agent-list.tsx │ │ ├── apiConfig.ts │ │ ├── cluster-create.tsx │ │ ├── cluster-edit.tsx │ │ ├── cluster-list.tsx │ │ ├── cluster-management.tsx │ │ ├── dashboard │ │ │ ├── agents-dashboard-table.tsx │ │ │ ├── agents-pie-chart.tsx │ │ │ ├── clusters-dashboard-table.tsx │ │ │ ├── clusters-pie-chart.tsx │ │ │ ├── dashboard-details-render.js │ │ │ ├── dashboard-details.js │ │ │ ├── dashboard-drawer.js │ │ │ ├── entries-dashboard-table.tsx │ │ │ ├── render-cell-expand.tsx │ │ │ ├── table │ │ │ │ └── dashboard-table.tsx │ │ │ ├── title.tsx │ │ │ └── tornjak-dashboard.js │ │ ├── entry-create-json.tsx │ │ ├── entry-create.tsx │ │ ├── entry-expiry-features.tsx │ │ ├── entry-list.tsx │ │ ├── error-api.tsx │ │ ├── federation-create.tsx │ │ ├── federation-list.tsx │ │ ├── helpers.ts │ │ ├── is_manager.ts │ │ ├── navbar-header-toolbar.tsx │ │ ├── navbar.tsx │ │ ├── select-server.tsx │ │ ├── server-management.tsx │ │ ├── spiffe-helper.tsx │ │ ├── spire-health-check.tsx │ │ ├── style.css │ │ ├── tornjak-api-helpers.tsx │ │ ├── tornjak-helper.tsx │ │ ├── tornjak-server-info.tsx │ │ ├── trustbundle-create.tsx │ │ ├── types.ts │ │ └── work-load-attestor-modal.tsx │ ├── data │ │ └── data.ts │ ├── env.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── redux │ │ ├── actions │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── reducers │ │ │ ├── agentsReducer.ts │ │ │ ├── authReducer.ts │ │ │ ├── clustersReducer.ts │ │ │ ├── entriesReducer.ts │ │ │ ├── federationsReducer.ts │ │ │ ├── index.ts │ │ │ ├── serversReducer.ts │ │ │ └── tornjakReducer.ts │ │ └── store.ts │ ├── reportWebVitals.js │ ├── res │ │ ├── tornjak_face.png │ │ └── tornjak_logo.png │ └── tables │ │ ├── agents-list-table.tsx │ │ ├── clusters-list-table.tsx │ │ ├── entries-list-table.tsx │ │ ├── federations-list-table.tsx │ │ ├── list-table.tsx │ │ ├── servers-list-table.tsx │ │ ├── table-body.tsx │ │ ├── table-head.tsx │ │ └── table-toolbar.tsx ├── tsconfig.json └── webpack.config.js ├── go.mod ├── go.sum ├── logos ├── logo+tornjak.1675x700.png ├── logo+tornjak.2132x1291.png ├── logo+tornjak.282x340.png ├── logo+tornjak.3300x1112.jpg ├── logo+tornjak.black.1808x791.png ├── logo+tornjak.black.2166x1297.png ├── logo+tornjak.black.3300x1112.png ├── logo+tornjak.black.3304x1114.jpg ├── logo.678x704.png ├── logo.858x944.png └── tornjak_logo.jpg ├── openapi.yaml ├── pkg ├── agent │ ├── authentication │ │ ├── authenticator │ │ │ ├── authenticator.go │ │ │ ├── keycloak.go │ │ │ └── null_authenticator.go │ │ └── user │ │ │ └── user.go │ ├── authorization │ │ ├── authorization.go │ │ ├── null_authorizer.go │ │ ├── rbac.go │ │ └── rbac_test.go │ ├── db │ │ ├── db.go │ │ ├── sqlite.go │ │ ├── sqlite_errors.go │ │ ├── sqlite_test.go │ │ └── sqlite_txhelpers.go │ ├── spirecrd │ │ ├── federation.go │ │ ├── helpers.go │ │ └── manager.go │ └── types │ │ ├── agentinfo.go │ │ └── clusterinfo.go └── manager │ ├── db │ ├── db.go │ ├── sqlite.go │ └── sqlite_test.go │ └── types │ ├── serverinfo.go │ └── types.go ├── scripts └── run_backend.sh └── version.txt /.github/actions/build/action.yaml: -------------------------------------------------------------------------------- 1 | name: 'Build Image' 2 | description: 'Builds and pushes images' 3 | inputs: 4 | image-tag-prefix: # prefix the tag with this 5 | description: 'IMAGE_TAG_PREFIX' 6 | required: false 7 | default: '' 8 | backend-dockerfile: # dockerfile for backend build 9 | description: 'DOCKERFILE_BACKEND' 10 | required: false 11 | default: Dockerfile.backend-container 12 | frontend-dockerfile: # dockerfile for frontend build 13 | description: 'DOCKERFILE_FRONTEND' 14 | required: false 15 | default: frontend/Dockerfile.frontend-container 16 | tag-version: # set to true if tagging official version 17 | description: 'VERSION' 18 | required: false 19 | default: false 20 | runs: 21 | using: "composite" 22 | steps: 23 | - name: Set image tag prefix 24 | shell: bash 25 | run: echo "IMAGE_TAG_PREFIX=${{ inputs.image-tag-prefix }}" >> $GITHUB_ENV 26 | - name: Set backend dockerfile 27 | shell: bash 28 | run: echo "DOCKERFILE_BACKEND=${{ inputs.backend-dockerfile }}" >> $GITHUB_ENV 29 | - name: Set frontend dockerfile 30 | shell: bash 31 | run: echo "DOCKERFILE_FRONTEND=${{ inputs.frontend-dockerfile }}" >> $GITHUB_ENV 32 | 33 | - name: Install Golang 34 | uses: actions/setup-go@v5.0.0 35 | with: 36 | go-version-file: go.mod 37 | check-latest: true 38 | cache: true 39 | 40 | - name: Download modules 41 | shell: bash 42 | run: go mod download 43 | 44 | - uses: actions/setup-node@v4.0.2 45 | with: 46 | node-version: '18' 47 | 48 | # https://github.com/docker/setup-qemu-action 49 | #- name: Set up QEMU 50 | # uses: docker/setup-qemu-action@v3 51 | # https://github.com/docker/setup-buildx-action 52 | - name: Set up Docker Buildx 53 | uses: docker/setup-buildx-action@v3 54 | 55 | - name: Download modules 56 | shell: bash 57 | run: go mod download 58 | 59 | - name: golangci-lint 60 | uses: golangci/golangci-lint-action@v8.0.0 61 | with: 62 | version: v2.1.6 63 | args: --timeout 7m 64 | 65 | # set repo and GITHUB SHA 66 | - name: Set github commit id 67 | shell: bash 68 | run: echo "GITHUB_SHA=$GITHUB_SHA" >> $GITHUB_ENV 69 | - name: Set release repo 70 | shell: bash 71 | run: echo "REPO=ghcr.io/${{ github.repository_owner }}" >> $GITHUB_ENV 72 | # override version with GITHUB_SHA if tag-version set to false 73 | # this way only GITHUBSHA is tagged with this build 74 | - name: Override version if not official build 75 | shell: bash 76 | if: ${{ inputs.tag-version == 'false' }} 77 | run: echo "VERSION=$GITHUB_SHA" >> $GITHUB_ENV 78 | 79 | # build and push images tagged with GITHUB_SHA, version 80 | - name: Build and push tornjak images 81 | shell: bash 82 | run: make release-images 83 | 84 | -------------------------------------------------------------------------------- /.github/compute-version/action.yaml: -------------------------------------------------------------------------------- 1 | name: Compute Version 2 | description: computes version of tornjak 3 | outputs: 4 | version: 5 | description: "tornjak version" 6 | value: ${{ steps.version.outputs.version }} 7 | runs: 8 | using: composite 9 | steps: 10 | - name: Generate Version 11 | id: version 12 | shell: bash 13 | run: | 14 | version="$(cat version.txt | cut -d '.' -f -2)" 15 | echo VERSION=$version 16 | echo "version=$version" >> $GITHUB_OUTPUT 17 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | # Workflow files stored in the 10 | # default location of `.github/workflows` 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | open-pull-requests-limit: 5 15 | -------------------------------------------------------------------------------- /.github/workflows/.ci.yaml.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/.github/workflows/.ci.yaml.swp -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Tornjak CI 2 | on: [push, pull_request] 3 | jobs: 4 | tornjak-build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Show trigger info 8 | run: | 9 | cat <>"${GITHUB_STEP_SUMMARY}" 10 | # Workflow job info 11 | 12 | - 🎉 The job was automatically triggered by a ${{ github.event_name }} event. 13 | - 🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub! 14 | - 🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}. 15 | EOF 16 | 17 | - name: Check out repository code 18 | uses: actions/checkout@v4.2.2 19 | 20 | - name: Install Golang 21 | uses: actions/setup-go@v5.5.0 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | cache: true 26 | 27 | - uses: actions/setup-node@v4.4.0 28 | with: 29 | node-version: '18' 30 | 31 | # https://github.com/docker/setup-qemu-action 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | # https://github.com/docker/setup-buildx-action 35 | - name: Set up Docker Buildx 36 | uses: docker/setup-buildx-action@v3 37 | 38 | - name: Download modules 39 | run: go mod download 40 | 41 | - name: golangci-lint 42 | uses: golangci/golangci-lint-action@v8.0.0 43 | with: 44 | version: v2.1.6 45 | args: --timeout 7m 46 | 47 | - name: Build binaries 48 | run: make binaries 49 | 50 | - name: Build images 51 | run: make images 52 | 53 | - name: Print job result 54 | run: | 55 | cat <>"${GITHUB_STEP_SUMMARY}" 56 | - 🍏 This job's status is ${{ job.status }}. 57 | EOF 58 | -------------------------------------------------------------------------------- /.github/workflows/linting.yaml: -------------------------------------------------------------------------------- 1 | name: Markdown Linter 2 | on: [pull_request, push] 3 | permissions: 4 | contents: read 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout markdown 10 | uses: actions/checkout@v4.2.2 11 | - name: Lint markdown 12 | uses: DavidAnson/markdownlint-cli2-action@v20 13 | continue-on-error: true 14 | with: 15 | config: '.markdownlint.yml' 16 | globs: | 17 | docs/*.md 18 | !examples/*.md -------------------------------------------------------------------------------- /.github/workflows/master-build.yaml: -------------------------------------------------------------------------------- 1 | name: Tornjak Artifact push 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'dependabot/**' 6 | workflow_dispatch: {} 7 | jobs: 8 | alpine-build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Show trigger info 12 | run: | 13 | cat <>"${GITHUB_STEP_SUMMARY}" 14 | # Workflow job info 15 | 16 | - 🎉 The job was automatically triggered by a ${{ github.event_name }} event. 17 | - 🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub! 18 | - 🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}. 19 | EOF 20 | 21 | - name: Check out repository code 22 | uses: actions/checkout@v4.2.2 23 | 24 | - name: Log in to GHCR.io 25 | uses: docker/login-action@v3.4.0 26 | with: 27 | registry: ghcr.io 28 | username: ${{ github.repository_owner }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Get branch name 32 | id: branch_name 33 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 34 | 35 | - name: Compute Tornjak version 36 | uses: ./.github/compute-version 37 | id: version 38 | 39 | - name: Run build 40 | uses: ./.github/actions/build 41 | with: 42 | tag-version: ${{ contains(fromJSON('["main", "${{ steps.version.outputs.version }}"]'), steps.branch_name.outputs.branch) && true || false }} 43 | 44 | - name: Print job result 45 | run: | 46 | cat <>"${GITHUB_STEP_SUMMARY}" 47 | - 🍏 This job's status is ${{ job.status }}. 48 | EOF 49 | ubi-build: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Show trigger info 53 | run: | 54 | cat <>"${GITHUB_STEP_SUMMARY}" 55 | # Workflow job info 56 | 57 | - 🎉 The job was automatically triggered by a ${{ github.event_name }} event. 58 | - 🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub! 59 | - 🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}. 60 | EOF 61 | 62 | - name: Check out repository code 63 | uses: actions/checkout@v4.2.2 64 | - name: Log in to GHCR.io 65 | uses: docker/login-action@v3.4.0 66 | with: 67 | registry: ghcr.io 68 | username: ${{ github.repository_owner }} 69 | password: ${{ secrets.GITHUB_TOKEN }} 70 | 71 | - name: Get branch name 72 | id: branch_name 73 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 74 | 75 | - name: Compute Tornjak version 76 | uses: ./.github/compute-version 77 | id: version 78 | 79 | - name: Run build 80 | uses: ./.github/actions/build 81 | with: 82 | image-tag-prefix: ubi- 83 | backend-dockerfile: Dockerfile.backend-container.ubi 84 | frontend-dockerfile: frontend/Dockerfile.frontend-container.ubi 85 | tag-version: ${{ contains(fromJSON('["main", "${{ steps.version.outputs.version }}"]'), steps.branch_name.outputs.branch) && true || false }} 86 | 87 | - name: Print job result 88 | run: | 89 | cat <>"${GITHUB_STEP_SUMMARY}" 90 | - 🍏 This job's status is ${{ job.status }}. 91 | EOF 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .gitignore 3 | 4 | # do not ignore tornjak backend 5 | !tornjak-backend/ 6 | 7 | # golang vendor 8 | vendor 9 | 10 | # dependencies 11 | frontend/node_modules 12 | frontend/.pnp 13 | frontend/.pnp.js 14 | 15 | # testing 16 | frontend/coverage 17 | 18 | # production 19 | frontend/build 20 | 21 | # misc 22 | .DS_Store 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | .eslintcache 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | serverlocaldb 34 | agentlocaldb 35 | 36 | tornjak-manager 37 | 38 | # build artifacts 39 | bin 40 | ui 41 | frontend-local-build 42 | .idea 43 | 44 | # accidental local builds 45 | node_modules 46 | package.json 47 | package-lock.json 48 | 49 | /agent 50 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ### Contributor Code of Conduct 2 | 3 | We follow the [CNCF Contributor Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). Additionally, we commit to the following guidelines as detailed on the [SPIFFE Code of Conduct](https://github.com/spiffe/spiffe/blob/master/CODE-OF-CONDUCT.md): 4 | 5 | ### Community Guidelines 6 | 7 | - Our goal is to foster an inclusive and diverse community of technology enthusiasts. 8 | 9 | - Try to be your best self. Treat your fellow community members with kindness and empathy. We welcome disagreements when they are conducted respectfully and without personal attacks. 10 | 11 | - We ask that you keep unstructured critique to a minimum. Disparaging remarks about the project are unnecessary and a drain on community morale. Feedback should be constructive and relevant. Having passionately held opinions on what should improve is encouraged! We hope you will use that enthusiasm to roll up your sleeves and get involved by submitting pull requests. We have additional guidelines on [how to ask constructive questions](https://github.com/linkerd/linkerd/wiki/How-To-Ask-Questions-in-Slack). 12 | 13 | - We don't tolerate insults, spamming, trolling, flaming, baiting, or harassment. We don't tolerate sexual language, imagery, or unwanted advances. Private harassment is also unacceptable. 14 | 15 | - We do our best to avoid [subtle-isms](https://www.recurse.com/manual#sub-sec-social-rules): small actions that make others feel uncomfortable. If you witness a subtle-ism, you may respectfully point it out to the person publicly or privately, or you may ask a moderator to say something. Accidentally saying something biased is common, expected, and readily forgiven. It is not in and of itself a bannable offense. 16 | 17 | ### Moderation 18 | 19 | - If you feel any of SPIFFE's Slack channels require moderation, please e-mail [SPIFFE's Technical Steering Committee (TSC)](mailto:tsc@spiffe.io). The TSC will issue a warning to users who don't follow this code of conduct. A second offense results in a temporary ban. A third offense warrants a permanent ban. It is at the moderator's discretion to un-ban offending users, or to immediately ban a toxic user without warning. 20 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lumjjb @mrsabath @mamy-CS @maia-iyer 2 | 3 | ########################################## 4 | # Maintainers 5 | ########################################## 6 | 7 | # Brandon Lum 8 | # Google 9 | # @lumjjb 10 | 11 | # Mariusz Sabath 12 | # IBM 13 | # @mrsabath 14 | 15 | # Mohammed Abdi 16 | # IBM 17 | # mamy-CS 18 | 19 | # Maia Iyer 20 | # IBM 21 | # maia-iyer -------------------------------------------------------------------------------- /Dockerfile.backend-container: -------------------------------------------------------------------------------- 1 | # xx is helper for cross-compilation 2 | FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1 AS xx 3 | 4 | FROM --platform=$BUILDPLATFORM golang:1.24-alpine3.21 AS builder 5 | RUN apk add build-base 6 | COPY . /usr/src/myapp 7 | WORKDIR /usr/src/myapp 8 | COPY --link --from=xx / / 9 | ARG TARGETOS 10 | ARG TARGETARCH 11 | ENV CGO_ENABLED=1 12 | 13 | RUN apk add clang lld 14 | RUN make vendor 15 | RUN xx-go --wrap 16 | RUN xx-apk add musl-dev gcc 17 | RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-alpine-linux-musl; fi && \ 18 | go build --tags 'sqlite_json' -mod=vendor -ldflags '-s -w -linkmode external -extldflags "-static"' -o bin/tornjak-backend ./cmd/agent/main.go 19 | 20 | FROM alpine AS runtime 21 | RUN mkdir -p /opt/tornjak 22 | 23 | WORKDIR /opt/tornjak 24 | ENTRYPOINT ["/opt/tornjak/run_backend.sh"] 25 | 26 | # Add init 27 | COPY scripts/run_backend.sh run_backend.sh 28 | COPY --from=builder /usr/src/myapp/bin/tornjak-backend tornjak-backend 29 | 30 | # add a version link to the image description 31 | ARG version 32 | ARG github_sha 33 | LABEL org.opencontainers.image.description="Tornjak backend ($version) Alpine based image: https://github.com/spiffe/tornjak/releases/tag/$version" \ 34 | org.opencontainers.image.source="https://github.com/spiffe/tornjak" \ 35 | org.opencontainers.image.documentation="https://github.com/spiffe/tornjak/tree/main/docs" 36 | 37 | # Additional labels 38 | LABEL architecture="amd64" \ 39 | build-date="" \ 40 | description="Tornjak Backend" \ 41 | io.k8s.description="Tornjak Backend" \ 42 | io.k8s.display-name="tornjak-backend" \ 43 | maintainer="" \ 44 | name="spiffe/tornjak-backend" \ 45 | release="$version" \ 46 | summary="Tornjak backend image" \ 47 | url="" \ 48 | vcs-ref="" \ 49 | vcs-type="" \ 50 | vendor="" \ 51 | version="$version" 52 | 53 | # create env. variables with the build details 54 | ENV VERSION=$version 55 | ENV GITHUB_SHA=$github_sha 56 | -------------------------------------------------------------------------------- /Dockerfile.backend-container.ubi: -------------------------------------------------------------------------------- 1 | # xx is helper for cross-compilation 2 | FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.3.0 AS xx 3 | 4 | FROM --platform=$BUILDPLATFORM golang:1.24-alpine3.21 AS builder 5 | RUN apk add build-base 6 | COPY . /usr/src/myapp 7 | WORKDIR /usr/src/myapp 8 | COPY --link --from=xx / / 9 | ARG TARGETOS 10 | ARG TARGETARCH 11 | ENV CGO_ENABLED=1 12 | 13 | RUN apk add clang lld 14 | RUN make vendor 15 | RUN xx-go --wrap 16 | RUN xx-apk add musl-dev gcc 17 | RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-alpine-linux-musl; fi && \ 18 | go build --tags 'sqlite_json' -mod=vendor -ldflags '-s -w -linkmode external -extldflags "-static"' -o bin/tornjak-backend ./cmd/agent/main.go 19 | 20 | FROM registry.access.redhat.com/ubi8-micro:latest AS runtime 21 | RUN mkdir -p /opt/tornjak 22 | 23 | WORKDIR /opt/tornjak 24 | ENTRYPOINT ["/opt/tornjak/run_backend.sh"] 25 | 26 | # Add init 27 | COPY scripts/run_backend.sh run_backend.sh 28 | COPY --from=builder /usr/src/myapp/bin/tornjak-backend tornjak-backend 29 | 30 | # add a version link to the image description 31 | ARG version 32 | ARG github_sha 33 | LABEL org.opencontainers.image.description="Tornjak backend ($version) UBI based image: https://github.com/spiffe/tornjak/releases/tag/$version" \ 34 | org.opencontainers.image.source="https://github.com/spiffe/tornjak" \ 35 | org.opencontainers.image.documentation="https://github.com/spiffe/tornjak/tree/main/docs" 36 | # replace UBI labels 37 | LABEL architecture="amd64" \ 38 | build-date="" \ 39 | description="Tornjak Backend" \ 40 | io.k8s.description="Tornjak Backend" \ 41 | io.k8s.display-name="tornjak-backend" \ 42 | maintainer="" \ 43 | name="spiffe/tornjak-backend" \ 44 | release="$version" \ 45 | summary="Tornjak backend UBI image" \ 46 | url="" \ 47 | vcs-ref="" \ 48 | vcs-type="" \ 49 | vendor="" \ 50 | version="$version" 51 | 52 | # create env. variables with the build details 53 | ENV VERSION=$version 54 | ENV GITHUB_SHA=$github_sha 55 | -------------------------------------------------------------------------------- /Dockerfile.tornjak-manager: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | WORKDIR / 4 | #COPY ui-manager ui-manager 5 | 6 | # Add init 7 | ENTRYPOINT ["/tornjak-manager"] 8 | 9 | RUN apk add curl 10 | COPY bin/tornjak-manager tornjak-manager 11 | 12 | # add a version link to the image description 13 | ARG version 14 | ARG github_sha 15 | LABEL org.opencontainers.image.description="Tornjak manager ($version): https://github.com/spiffe/tornjak/releases/tag/$version" \ 16 | org.opencontainers.image.source="https://github.com/spiffe/tornjak" \ 17 | org.opencontainers.image.documentation="https://github.com/spiffe/tornjak/tree/main/docs" 18 | # create env. variables with the build details 19 | ENV VERSION=$version 20 | ENV GITHUB_SHA=$github_sha 21 | -------------------------------------------------------------------------------- /api/manager/api.go: -------------------------------------------------------------------------------- 1 | package managerapi 2 | 3 | import ( 4 | managertypes "github.com/spiffe/tornjak/pkg/manager/types" 5 | 6 | "github.com/pkg/errors" 7 | //types "github.com/spiffe/spire/proto/spire/types" 8 | //agent "github.com/spiffe/spire/proto/spire/api/server/agent/v1" 9 | //entry "github.com/spiffe/spire/proto/spire/api/server/entry/v1" 10 | ) 11 | 12 | type ListServersRequest struct{} 13 | type ListServersResponse managertypes.ServerInfoList 14 | 15 | func (s *Server) ListServers(inp ListServersRequest) (*ListServersResponse, error) { 16 | 17 | resp, err := s.db.GetServers() 18 | if err != nil { 19 | return nil, err 20 | } 21 | for i := range resp.Servers { 22 | resp.Servers[i].Key = []byte{} 23 | resp.Servers[i].Cert = []byte{} 24 | } 25 | 26 | return (*ListServersResponse)(&resp), nil 27 | } 28 | 29 | type RegisterServerRequest managertypes.ServerInfo 30 | 31 | func (s *Server) RegisterServer(inp RegisterServerRequest) error { 32 | sinfo := managertypes.ServerInfo(inp) 33 | if len(sinfo.Name) == 0 || len(sinfo.Address) == 0 { 34 | return errors.New("Server info missing mandatory fields") 35 | } 36 | 37 | return s.db.CreateServerEntry(sinfo) 38 | } 39 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | managerapi "github.com/spiffe/tornjak/api/manager" 7 | ) 8 | 9 | func main() { 10 | var ( 11 | dbString = "./serverlocaldb" 12 | listenAddr = ":50000" 13 | ) 14 | s, err := managerapi.NewManagerServer(listenAddr, dbString) 15 | if err != nil { 16 | log.Fatalf("err: %v", err) 17 | } 18 | s.HandleRequests() 19 | } 20 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Index 2 | 3 | - [Tornjak Deployment Using Helm-Charts](/docs/helm/tornjak-helm-chart.md) 4 | 5 | - [Blogs](/docs/blogs.md) 6 | 7 | - [Debugging, Hints and Tips for Solving Common Problems with Tornjak](/docs/tornjak-hints.md) 8 | - [Tornjak Deployment](/docs/tornjak-hints.md#tornjak-deployment) 9 | - [Tornjak Configuration](/docs/tornjak-hints.md#tornjak-configuration) 10 | - [User Management](/docs/tornjak-hints.md#user-management) 11 | 12 | - [JSON Format for New Entry](/docs/newEntry-json-format.md) 13 | 14 | - [Server plugin: Authentication "Keycloak"](/docs/plugins/plugin_server_authentication_keycloak.md) 15 | 16 | - [Server plugin: Authorization "RBAC"](/docs/plugins/plugin_server_authorization_rbac.md) 17 | 18 | - [Server plugin: Datastore "SQL"](/docs/plugins/plugin_server_datastore_sql.md) 19 | 20 | - [Server plugin: SPIRECRDManager](/docs/plugins/plugin_server_spirecrd.md) 21 | - [Tornjak Agent](/docs/tornjak-agent.md) 22 | - [Tornjak SPIRE Server Agent](/docs/tornjak-agent.md#tornjak-spire-server-agent) 23 | - [APIs](/docs/tornjak-agent.md#apis) 24 | - [Authentication](/docs/tornjak-agent.md#authentication) 25 | - [Authorization](/docs/tornjak-agent.md#authorization) 26 | 27 | - [Tornjak Manager](/docs/tornjak-manager.md) 28 | 29 | - [Tornjak Plan](/docs/newEntry-json-format.md) 30 | 31 | - [Tornjak Server Configuration Reference](/docs/config-tornjak-server.md) 32 | - [Command line options](/docs/config-tornjak-server.md#command-line-options) 33 | - [The Tornjak Config](/docs/config-tornjak-server.md#the-tornjak-config) 34 | - [General Tornjak Server Configs](/docs/config-tornjak-server.md#general-tornjak-server-configs) 35 | - [About Tornjak Plugins](/docs/config-tornjak-server.md#about-tornjak-plugins) 36 | - [Sample Configuration Files](/docs/config-tornjak-server.md#sample-configuration-files) 37 | - [Further Reading](/docs/config-tornjak-server.md#further-reading) 38 | 39 | - [Tornjak UI-API Documentation](/docs/tornjak-ui-api-documentation.md) 40 | - [Overview](/docs/tornjak-ui-api-documentation.md#11-overview) 41 | - [Tornjak User Interface (UI) Architecture](/docs/tornjak-ui-api-documentation.md#2-tornjak-user-interface-ui-architecture) 42 | - [UI Pages (With their paths)](/docs/tornjak-ui-api-documentation.md#21-ui-pages-with-their-paths) 43 | - [Tornjak User Interface (UI) Interaction with API Endpoints](/docs/tornjak-ui-api-documentation.md#3-tornjak-user-interface-ui-interaction-with-api-endpoints) 44 | - [Agent API’s](/docs/tornjak-ui-api-documentation.md#31-tornjak-apis) 45 | - [Manager API’s](/docs/tornjak-ui-api-documentation.md#32-manager-apis) 46 | - [Tornjak Redux Reducers and Actions With Their Respective Descriptions](/docs/tornjak-ui-api-documentation.md#4-tornjak-redux-reducers-and-actions-with-their-respective-descriptions) 47 | 48 | - [User Management](/docs/user-management.md) 49 | - [Overview](/docs/user-management.md#overview) 50 | - [Architecture](/docs/user-management.md#architecture) 51 | - [General Deployment](/docs/user-management.md#general-deployment) 52 | - [Examples and Tutorials](/docs/user-management.md#examples-and-tutorials) 53 | -------------------------------------------------------------------------------- /docs/architecture-diagrams/Tornjak Agent Mode Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/architecture-diagrams/Tornjak Agent Mode Architecture.png -------------------------------------------------------------------------------- /docs/architecture-diagrams/Tornjak Architecture with IAM Feb 2023.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/architecture-diagrams/Tornjak Architecture with IAM Feb 2023.pptx -------------------------------------------------------------------------------- /docs/architecture-diagrams/Tornjak Manager Mode Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/architecture-diagrams/Tornjak Manager Mode Architecture.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/browser-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/browser-flow.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/github-keycloak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/github-keycloak.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/github-oauth-app-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/github-oauth-app-secret.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/github-oauth-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/github-oauth-app.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/google-cloud-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/google-cloud-console.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/google-cloud-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/google-cloud-credentials.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/google-keycloak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/google-keycloak.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/identity-providers-homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/identity-providers-homepage.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/keycloak-sign-in-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/keycloak-sign-in-page.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/microsoft-azure-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/microsoft-azure-overview.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/microsoft-azure-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/microsoft-azure-secret.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/microsoft-azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/microsoft-azure.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/microsoft-keycloak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/microsoft-keycloak.png -------------------------------------------------------------------------------- /docs/auth/keycloak/diagrams/upstream-architecture-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/auth/keycloak/diagrams/upstream-architecture-diagram.png -------------------------------------------------------------------------------- /docs/blogs.md: -------------------------------------------------------------------------------- 1 | # Tornjak Blogs 2 | 3 | | Published Date | Title | Abstract | 4 | |----------------| ------|----------| 5 | | 2022-12-08 | [Guide to integrating Tornjak with Keycloak for access control to SPIRE](https://medium.com/universal-workload-identity/guide-to-integrating-tornjak-with-keycloak-for-access-control-to-spire-40a3d5ee5f5a) | Instructions for Tornjak Setup to support integration with Keycloak | 6 | | 2022-11-16 | [Guide To Keycloak Configuration For Tornjak](https://medium.com/universal-workload-identity/step-by-step-guide-to-setup-keycloak-configuration-for-tornjak-dbe5c3049034) | Instructions for Keycloak Setup to support Tornjak users | 7 | | 2022-10-26 | [Identity Access Management (IAM) Integration With Tornjak](https://medium.com/universal-workload-identity/identity-access-management-iam-integration-with-tornjak-749984966ab5) | Overview of IAM for Tornjak | 8 | | 2022-03-11 | [Tornjak Open Source Project](https://medium.com/universal-workload-identity/tornjak-open-source-project-under-cncf-spifee-spire-43eb974e4bc9) | Overview of Tornjak architecture | 9 | | 2021-09-10 | [Deploying Tornjak with Helm Charts](https://medium.com/universal-workload-identity/deploying-tornjak-with-helm-charts-e51fc21b962c) | How to deploy Tornjak with Helm charts | 10 | | 2021-09-01 | [Untangling the Multi-cloud Identity and Access Control Problem](https://medium.com/universal-workload-identity/untangling-the-multi-cloud-identity-and-access-control-problem-ba4a51ec0e4a) | Overview of how Tornjak helps solving identity problems in multi-cloud deployments| 11 | | 2021-08-23 | [Shepherding your Cloud Native “cattle” with Tornjak](https://medium.com/universal-workload-identity/shepherding-your-cloud-native-cattle-with-tornjak-eb0b9a7c96bc) | Introduction to Tornjak | 12 | | 2021-08-26 | [Open source workload identity management could help secure hybrid clouds](https://research.ibm.com/blog/tornjak-project-cncf) | IBM donates Tornjak to CNCF | 13 | 14 | [Tornjak Channel on Medium](https://medium.com/universal-workload-identity) 15 | -------------------------------------------------------------------------------- /docs/conf/agent/base.conf: -------------------------------------------------------------------------------- 1 | server { 2 | # location of SPIRE socket 3 | # here, set to default SPIRE socket path 4 | spire_socket_path = "unix:///tmp/spire-server/private/api.sock" 5 | 6 | # [required] configure HTTP connection to Tornjak server 7 | http { 8 | port = 10000 # opens at port 10000 9 | } 10 | 11 | } 12 | 13 | plugins { 14 | DataStore "sql" { # local database plugin 15 | plugin_data { 16 | drivername = "sqlite3" 17 | filename = "/run/spire/data/tornjak.sqlite3" # stores locally in this file 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /docs/helm/tornjak-helm-chart.md: -------------------------------------------------------------------------------- 1 | # Deploying Tornjak via Helm Charts 2 | 3 | ## Overview 4 | 5 | You can deploy **Tornjak** using the [SPIFFE helm-charts-hardened repository](https://github.com/spiffe/helm-charts-hardened). 6 | 7 | This guide walks you through deploying both the frontend and backend of Tornjak with Direct Access, using Helm charts in a local Kubernetes environment via Minikube. 8 | 9 | By the end, you’ll have a working instance of SPIRE integrated with Tornjak for easier visibility and management of your SPIFFE identities. 10 | 11 | ## Prerequisites 12 | 13 | Make sure you have the following installed on your system: 14 | 15 | - [Minikube](https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Fx86-64%2Fstable%2Fbinary+download) 16 | - [Helm](https://helm.sh/docs/intro/install/) 17 | 18 | ## Step-by-Step Deployment 19 | 20 | ### 1. Clone Repo 21 | 22 | ```sh 23 | git clone https://github.com/spiffe/tornjak.git 24 | cd tornjak 25 | cd docs/helm 26 | ``` 27 | 28 | ### 2. Start Minikube 29 | 30 | ```sh 31 | minikube start 32 | ``` 33 | 34 | ### 3. Deploy SPIRE 35 | 36 | Now we can excute the following to deploy our SPIRE instance: 37 | 38 | ```sh 39 | helm upgrade --install -n spire-server spire-crds spire-crds --repo https://spiffe.github.io/helm-charts-hardened/ --create-namespace 40 | ``` 41 | 42 | ### 4. Deploy Tornjak 43 | 44 | Now we can deploy Tornjak with SPIRE. 45 | 46 | First, we need to export the Tornjak backend API URL: 47 | 48 | ```sh 49 | export TORNJAK_API=http://localhost:10000 50 | ``` 51 | 52 | Then, run the following Helm command to deploy Tornjak with the frontend and backend enabled: 53 | 54 | ```sh 55 | helm upgrade --install -n spire-server spire spire \ 56 | --repo https://spiffe.github.io/helm-charts-hardened/ \ 57 | --set tornjak-frontend.apiServerURL=$TORNJAK_API \ 58 | --values values.yaml \ 59 | --render-subchart-notes 60 | ``` 61 | 62 | ### 5. Test Deployment 63 | 64 | You can verify the deployment with: 65 | 66 | ```sh 67 | helm test spire -n spire-server 68 | ``` 69 | 70 | ### 6. Access Tornjak UI 71 | 72 | Run the backend. 73 | 74 | ```sh 75 | kubectl -n spire-server port-forward service/spire-tornjak-backend 10000:10000 76 | ``` 77 | 78 | In a separate terminal, run the frontend. 79 | 80 | ```sh 81 | kubectl -n spire-server port-forward service/spire-tornjak-frontend 3000:3000 82 | ``` 83 | 84 | Open your browser and go to [http://localhost:3000](http://localhost:3000) 85 | You should now see the Tornjak UI! 86 | -------------------------------------------------------------------------------- /docs/helm/values.yaml: -------------------------------------------------------------------------------- 1 | spire-server: 2 | tornjak: 3 | enabled: true 4 | 5 | tornjak-frontend: 6 | enabled: true 7 | service: 8 | type: ClusterIP 9 | port: 3000 10 | resources: 11 | requests: 12 | cpu: 50m 13 | memory: 128Mi 14 | limits: 15 | cpu: 100m 16 | memory: 512Mi 17 | -------------------------------------------------------------------------------- /docs/newEntry-json-format.md: -------------------------------------------------------------------------------- 1 | ## JSON Format for New Entry 2 | 3 | ```json 4 | { 5 | "entries": [ 6 | { 7 | "spiffe_id": { //required - must be a valid spiffe_id 8 | "trust_domain": "example.org", 9 | "path": "/sample_path1" 10 | }, 11 | "parent_id": { //required - must be a valid parent_id 12 | "trust_domain": "example.org", 13 | "path": "/spire/agent/k8s_psat/demo-cluster/21b781e7-4c92-4fe9-8bb2-108756fa9de2" 14 | }, 15 | "selectors": [ //required - must be a valid type:value format 16 | { 17 | "type": "k8s", 18 | "value": "container-image:ere" 19 | }, 20 | { 21 | "type": "k8s", 22 | "value": "container-name:eww" 23 | }, 24 | { 25 | "type": "k8s", 26 | "value": "node-name:qq" 27 | } 28 | ], 29 | "admin": true, //optional 30 | "ttl": 40, //optional 31 | "expires_at": 34, //optional 32 | "downstream": true, //optional 33 | "federates_with": [ //optional 34 | "example.org" 35 | ], 36 | "dns_names": [ //optional 37 | "example.org", 38 | "abc.com" 39 | ] 40 | } 41 | ] 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/newFederation-json-format.md: -------------------------------------------------------------------------------- 1 | ## JSON Format for New Federation 2 | 3 | ```json 4 | { 5 | "federation_relationships": [ 6 | { 7 | "trust_domain": "server.org", 8 | "bundle_endpoint_url": "https://host.docker.internal:8440", 9 | "https_spiffe": { 10 | "endpoint_spiffe_id": "spiffe://server.org/spire/server" 11 | }, 12 | "trust_domain_bundle": { 13 | "trust_domain": "server.org", 14 | "x509_authorities": [ 15 | { 16 | "asn1": "MIID3TCCAsWg... (truncated)" 17 | }, 18 | { 19 | "asn1": "MIID3TCCAsWg... (truncated)" 20 | }, 21 | { 22 | "asn1": "MIID3TCCAsWg... (truncated)" 23 | } 24 | ], 25 | "jwt_authorities": [ 26 | { 27 | "public_key": "MIIBIjANBgkqh... (truncated)", 28 | "key_id": "hZinOYBqM3jGnq...", 29 | "expires_at": 1734022841 30 | }, 31 | { 32 | "public_key": "MIIBIjANBgkqh... (truncated)", 33 | "key_id": "3o0DnZN5clyzR...", 34 | "expires_at": 1734113731 35 | }, 36 | { 37 | "public_key": "MIIBIjANBgkqh... (truncated)", 38 | "key_id": "ngoXDgR1SWATM...", 39 | "expires_at": 1734191531 40 | } 41 | ], 42 | "sequence_number": 18 43 | } 44 | } 45 | ] 46 | } 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/plugins/plugin_server_authentication_keycloak.md: -------------------------------------------------------------------------------- 1 | # Server plugin: Authentication "Keycloak" 2 | 3 | Please see our documentation on the [authorization feature](./user-management.md) for more complete details. 4 | 5 | Note that simply enabling this feature will NOT enable authorization. In order to apply authorization logic to user details, one must also enable an Authorization plugin. Any output from this layer, including authentication errors, are to be interpreted by an Authorization layer. 6 | 7 | The configuration has the following key-value pairs: 8 | 9 | | Key | Description | Required | 10 | | ----------- | ----------------------------------------------------------------------- | ------------------- | 11 | | issuer | Issuer URL for OIDC Discovery with external IAM System | True | 12 | | audience | Expected audience value in received JWT tokens | False (Recommended) | 13 | 14 | A sample configuration file for syntactic referense is below: 15 | 16 | ```hcl 17 | Authenticator "Keycloak" { 18 | plugin_data { 19 | issuer = "http://host.docker.internal:8080/realms/tornjak" 20 | audience = "tornjak-backend" 21 | } 22 | } 23 | ``` 24 | 25 | NOTE: If audience field is missing or empty, the server will log a warning and NOT perform an audience check. 26 | It is highly recommended `audience` is populated to ensure only tokens meant for the Tornjak Backend are accepted. 27 | 28 | ## User Info extracted 29 | 30 | This plugin assumes roles are available in `realm_access.roles` in the JWT and passes this list as user.roles. 31 | 32 | These mapped values are passed to the authorization layer. 33 | -------------------------------------------------------------------------------- /docs/plugins/plugin_server_authorization_rbac.md: -------------------------------------------------------------------------------- 1 | # Server plugin: Authorization "RBAC" 2 | 3 | Please see our documentation on the [authorization feature](./user-management.md) for more complete details. 4 | 5 | This configuration has the following inputs: 6 | 7 | | Key | Description | Required | 8 | | --- | ----------- | -------- | 9 | | name | name of the policy for logging purposes | no | 10 | | `role "" {desc = ""}` | `` is the name of a role that can be allowed access; `` is a short description | no | 11 | | `API "" {allowed_roles = ["", ...]}` | `` is the name of the API that will allow access to roles listed such as `` | no | 12 | 13 | There can (and likely will be) multiple `role` and `API` blocks. If there are no role blocks, no API will be allowed any access. If there is a missing API block, no access will be granted for that API. 14 | 15 | A sample configuration file for syntactic referense is below: 16 | 17 | ```hcl 18 | Authorizer "RBAC" { 19 | plugin_data { 20 | name = "Admin Viewer Policy" 21 | role "admin" { desc = "admin person" } 22 | role "viewer" { desc = "viewer person" } 23 | role "" { desc = "authenticated person" } 24 | 25 | API "/" { allowed_roles = [""] } 26 | API "/api/healthcheck" { allowed_roles = ["admin", "viewer"] } 27 | API "/api/debugserver" { allowed_roles = ["admin", "viewer"] } 28 | API "/api/agent/list" { allowed_roles = ["admin", "viewer"] } 29 | API "/api/entry/list" { allowed_roles = ["admin", "viewer"] } 30 | API "/api/tornjak/serverinfo" { allowed_roles = ["admin", "viewer"] } 31 | API "/api/tornjak/selectors/list" { allowed_roles = ["admin", "viewer"] } 32 | API "/api/tornjak/agents/list" { allowed_roles = ["admin", "viewer"] } 33 | API "/api/tornjak/clusters/list" { allowed_roles = ["admin", "viewer"] } 34 | API "/api/agent/ban" { allowed_roles = ["admin"] } 35 | API "/api/agent/delete" { allowed_roles = ["admin"] } 36 | API "/api/agent/createjointoken" { allowed_roles = ["admin"] } 37 | API "/api/entry/create" { allowed_roles = ["admin"] } 38 | API "/api/entry/delete" { allowed_roles = ["admin"] } 39 | API "/api/tornjak/selectors/register" { allowed_roles = ["admin"] } 40 | API "/api/tornjak/clusters/create" { allowed_roles = ["admin"] } 41 | API "/api/tornjak/clusters/edit" { allowed_roles = ["admin"] } 42 | API "/api/tornjak/clusters/delete" { allowed_roles = ["admin"] } 43 | } 44 | } 45 | ``` 46 | 47 | NOTE: If this feature is enabled without an authentication layer, it will render all calls uncallable. 48 | 49 | The above specification assumes roles `admin` and `viewer` are passed by the authentication layer. In this example, the following apply: 50 | 51 | 1. If user has `admin` role, can perform any call 52 | 2. If user has `viewer` role, can perform all read-only calls (See lists below) 53 | 3. If user is authenticated with no role, can perform only `/` Tornjak home call. 54 | 55 | ## Valid inputs 56 | 57 | There are a couple failure cases in which the plugin will fail to initialize and the Tornjak backend will not run: 58 | 59 | 1. If an included API block has an undefined API (`API "" {...}` where `x` is not a Tornjak API) 60 | 2. If an included API block has an undefined role (There exists `API "" {allowed_roles = [..., "", ...]}` such that for all `role "" {...}`, `y != z`) 61 | 62 | ## The empty string role "" 63 | 64 | If there is a role listed with name `""`, this enables some APIs to allow all users where the authentication layer does not return error. In the above example, only the `/` API has this behavior. 65 | 66 | ## Additional behavior specification 67 | 68 | If there is a role that is not included as an `allowed_role` in any API block, a user will not be granted access to any API based on that role. 69 | -------------------------------------------------------------------------------- /docs/plugins/plugin_server_datastore_sql.md: -------------------------------------------------------------------------------- 1 | # Server plugin: Datastore "SQL" 2 | 3 | Note the Datastore is a required plugin, and currently, as the SQL datastore is the only supported instance of the datastore plugin, there must be a section configuring this upon Tornjak backend startup. 4 | 5 | The configuration has the following key-value pairs: 6 | 7 | | Key | Description | Required | 8 | | ----------- | ---------------------------- | ------------------- | 9 | | drivername | Driver for SQL database | True | 10 | | filename | Location of database | True | 11 | 12 | A sample configuration file for syntactic reference is below: 13 | 14 | ```hcl 15 | DataStore "sql" { 16 | plugin_data { 17 | issuer = "sqlite3" 18 | audience = "/run/spire/data/tornjak.sqlite3" 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/plugins/plugin_server_spirecrd.md: -------------------------------------------------------------------------------- 1 | # Server plugin: SPIRECRDManager 2 | 3 | Note the SPIRECRDManager is an optional plugin. This plugin enables the creation of SPIRE CRDs on the cluster Tornjak is deployed on. It enables the following API calls: 4 | 5 | - `GET /api/v1/spire-controller-manager/clusterfederatedtrustdomains` 6 | 7 | > [!IMPORTANT] 8 | > This plugin requires two things: (a) That Tornjak is deployed in the same cluster as the relevant CRDs as it uses its own service account token to talk to the kube API server. (b) That the proper permissions are given to the Service Account token that Tornjak will use. Current Helm charts deploy SPIRE Controller manager and Tornjak in the same pod as the SPIRE server, so no extra configuration is necessary if deployed this way. 9 | 10 | The configuration has the following key-value pairs: 11 | 12 | | Key | Description | Required | 13 | | ---------- | -------------------------------- | ------------------- | 14 | | classname | className label for created CRDs | False | 15 | 16 | A sample configuration file for syntactic reference is below: 17 | 18 | ```hcl 19 | SPIREControllerManager { 20 | plugin_data { 21 | classname = "spire-mgmt-spire" 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/quickstart/agent-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: spire-agent 5 | namespace: spire 6 | -------------------------------------------------------------------------------- /docs/quickstart/agent-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | # Required cluster role to allow spire-agent to query k8s API server 2 | kind: ClusterRole 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: spire-agent-cluster-role 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["pods","nodes","nodes/proxy"] 9 | verbs: ["get"] 10 | 11 | --- 12 | # Binds above cluster role to spire-agent service account 13 | kind: ClusterRoleBinding 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | metadata: 16 | name: spire-agent-cluster-role-binding 17 | subjects: 18 | - kind: ServiceAccount 19 | name: spire-agent 20 | namespace: spire 21 | roleRef: 22 | kind: ClusterRole 23 | name: spire-agent-cluster-role 24 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /docs/quickstart/agent-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: spire-agent 5 | namespace: spire 6 | data: 7 | agent.conf: | 8 | agent { 9 | data_dir = "/run/spire" 10 | log_level = "DEBUG" 11 | server_address = "spire-server" 12 | server_port = "8081" 13 | socket_path = "/run/spire/sockets/agent.sock" 14 | trust_bundle_path = "/run/spire/bundle/bundle.crt" 15 | trust_domain = "example.org" 16 | } 17 | 18 | plugins { 19 | NodeAttestor "k8s_psat" { 20 | plugin_data { 21 | # NOTE: Change this to your cluster name 22 | cluster = "demo-cluster" 23 | } 24 | } 25 | 26 | KeyManager "memory" { 27 | plugin_data { 28 | } 29 | } 30 | 31 | WorkloadAttestor "k8s" { 32 | plugin_data { 33 | # Defaults to the secure kubelet port by default. 34 | # Minikube does not have a cert in the cluster CA bundle that 35 | # can authenticate the kubelet cert, so skip validation. 36 | skip_kubelet_verification = true 37 | node_name_env = "MY_NODE_NAME" 38 | } 39 | } 40 | 41 | WorkloadAttestor "unix" { 42 | plugin_data { 43 | } 44 | } 45 | } 46 | 47 | health_checks { 48 | listener_enabled = true 49 | bind_address = "0.0.0.0" 50 | bind_port = "8080" 51 | live_path = "/live" 52 | ready_path = "/ready" 53 | } 54 | -------------------------------------------------------------------------------- /docs/quickstart/agent-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: spire-agent 5 | namespace: spire 6 | labels: 7 | app: spire-agent 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: spire-agent 12 | template: 13 | metadata: 14 | namespace: spire 15 | labels: 16 | app: spire-agent 17 | spec: 18 | hostPID: true 19 | hostNetwork: true 20 | dnsPolicy: ClusterFirstWithHostNet 21 | serviceAccountName: spire-agent 22 | initContainers: 23 | - name: init 24 | # This is a small image with wait-for-it, choose whatever image 25 | # you prefer that waits for a service to be up. This image is built 26 | # from https://github.com/lqhl/wait-for-it 27 | image: cgr.dev/chainguard/wait-for-it 28 | args: ["-t", "30", "spire-server:8081"] 29 | containers: 30 | - name: spire-agent 31 | image: ghcr.io/spiffe/spire-agent:1.11.1 32 | args: ["-config", "/run/spire/config/agent.conf"] 33 | env: 34 | - name: MY_NODE_NAME 35 | valueFrom: 36 | fieldRef: 37 | fieldPath: status.podIP 38 | volumeMounts: 39 | - name: spire-config 40 | mountPath: /run/spire/config 41 | readOnly: true 42 | - name: spire-bundle 43 | mountPath: /run/spire/bundle 44 | - name: spire-agent-socket 45 | mountPath: /run/spire/sockets 46 | readOnly: false 47 | - name: spire-token 48 | mountPath: /var/run/secrets/tokens 49 | livenessProbe: 50 | httpGet: 51 | path: /live 52 | port: 8080 53 | failureThreshold: 2 54 | initialDelaySeconds: 15 55 | periodSeconds: 60 56 | timeoutSeconds: 3 57 | readinessProbe: 58 | httpGet: 59 | path: /ready 60 | port: 8080 61 | initialDelaySeconds: 5 62 | periodSeconds: 5 63 | volumes: 64 | - name: spire-config 65 | configMap: 66 | name: spire-agent 67 | - name: spire-bundle 68 | configMap: 69 | name: spire-bundle 70 | - name: spire-agent-socket 71 | hostPath: 72 | path: /run/spire/sockets 73 | type: DirectoryOrCreate 74 | - name: spire-token 75 | projected: 76 | sources: 77 | - serviceAccountToken: 78 | path: spire-agent 79 | expirationSeconds: 7200 80 | audience: spire-server 81 | -------------------------------------------------------------------------------- /docs/quickstart/client-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: client 5 | labels: 6 | app: client 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: client 11 | template: 12 | metadata: 13 | labels: 14 | app: client 15 | spec: 16 | hostPID: true 17 | hostNetwork: true 18 | dnsPolicy: ClusterFirstWithHostNet 19 | containers: 20 | - name: client 21 | image: ghcr.io/spiffe/spire-agent:1.10.0 22 | command: ["/opt/spire/bin/spire-agent"] 23 | args: [ "api", "watch", "-socketPath", "/run/spire/sockets/agent.sock" ] 24 | volumeMounts: 25 | - name: spire-agent-socket 26 | mountPath: /run/spire/sockets 27 | readOnly: true 28 | volumes: 29 | - name: spire-agent-socket 30 | hostPath: 31 | path: /run/spire/sockets 32 | type: Directory 33 | -------------------------------------------------------------------------------- /docs/quickstart/create-node-registration-entry.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | set -e 4 | 5 | bb=$(tput bold) 6 | nn=$(tput sgr0) 7 | 8 | 9 | echo "${bb}Creating registration entry for the node...${nn}" 10 | kubectl exec -n spire spire-server-0 -- \ 11 | /opt/spire/bin/spire-server entry create \ 12 | -node \ 13 | -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \ 14 | -selector k8s_psat:cluster:demo-cluster \ 15 | -selector k8s_psat:agent_ns:spire \ 16 | -selector k8s_psat:agent_sa:spire-agent 17 | -------------------------------------------------------------------------------- /docs/quickstart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: spire 5 | 6 | resources: 7 | - spire-namespace.yaml 8 | - agent-account.yaml 9 | - agent-cluster-role.yaml 10 | - agent-configmap.yaml 11 | - agent-daemonset.yaml 12 | - server-account.yaml 13 | - server-cluster-role.yaml 14 | - server-configmap.yaml 15 | - server-service.yaml 16 | - server-statefulset.yaml 17 | - spire-bundle-configmap.yaml 18 | 19 | -------------------------------------------------------------------------------- /docs/quickstart/server-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | -------------------------------------------------------------------------------- /docs/quickstart/server-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | # Role (namespace scoped) to be able to push certificate bundles to a configmap 2 | kind: Role 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: spire-server-configmap-role 6 | namespace: spire 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["configmaps"] 10 | verbs: ["patch", "get", "list"] 11 | --- 12 | # Binds above role to spire-server service account 13 | kind: RoleBinding 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | metadata: 16 | name: spire-server-configmap-role-binding 17 | namespace: spire 18 | subjects: 19 | - kind: ServiceAccount 20 | name: spire-server 21 | namespace: spire 22 | roleRef: 23 | apiGroup: rbac.authorization.k8s.io 24 | kind: Role 25 | name: spire-server-configmap-role 26 | --- 27 | # ClusterRole to allow spire-server node attestor to read pods and nodes, and query Token Review API 28 | kind: ClusterRole 29 | apiVersion: rbac.authorization.k8s.io/v1 30 | metadata: 31 | name: spire-server-trust-role 32 | rules: 33 | - apiGroups: [""] 34 | resources: ["pods", "nodes"] 35 | verbs: ["get"] 36 | - apiGroups: ["authentication.k8s.io"] 37 | resources: ["tokenreviews"] 38 | verbs: ["create"] 39 | --- 40 | # Binds above cluster role to spire-server service account 41 | kind: ClusterRoleBinding 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | metadata: 44 | name: spire-server-trust-role-binding 45 | subjects: 46 | - kind: ServiceAccount 47 | name: spire-server 48 | namespace: spire 49 | roleRef: 50 | kind: ClusterRole 51 | name: spire-server-trust-role 52 | apiGroup: rbac.authorization.k8s.io 53 | -------------------------------------------------------------------------------- /docs/quickstart/server-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | data: 7 | server.conf: | 8 | server { 9 | bind_address = "0.0.0.0" 10 | bind_port = "8081" 11 | socket_path = "/tmp/spire-server/private/api.sock" 12 | trust_domain = "example.org" 13 | data_dir = "/run/spire/data" 14 | log_level = "DEBUG" 15 | #AWS requires the use of RSA. EC cryptography is not supported 16 | ca_key_type = "rsa-2048" 17 | 18 | ca_subject = { 19 | country = ["US"], 20 | organization = ["SPIFFE"], 21 | common_name = "", 22 | } 23 | } 24 | 25 | plugins { 26 | DataStore "sql" { 27 | plugin_data { 28 | database_type = "sqlite3" 29 | connection_string = "/run/spire/data/datastore.sqlite3" 30 | } 31 | } 32 | 33 | NodeAttestor "k8s_psat" { 34 | plugin_data { 35 | clusters = { 36 | # NOTE: Change this to your cluster name 37 | "demo-cluster" = { 38 | service_account_allow_list = ["spire:spire-agent"] 39 | } 40 | } 41 | } 42 | } 43 | 44 | KeyManager "disk" { 45 | plugin_data { 46 | keys_path = "/run/spire/data/keys.json" 47 | } 48 | } 49 | 50 | Notifier "k8sbundle" { 51 | plugin_data { 52 | } 53 | } 54 | } 55 | 56 | health_checks { 57 | listener_enabled = true 58 | bind_address = "0.0.0.0" 59 | bind_port = "8080" 60 | live_path = "/live" 61 | ready_path = "/ready" 62 | } 63 | -------------------------------------------------------------------------------- /docs/quickstart/server-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | spec: 7 | type: NodePort 8 | ports: 9 | - name: grpc 10 | port: 8081 11 | targetPort: 8081 12 | protocol: TCP 13 | selector: 14 | app: spire-server 15 | --- 16 | apiVersion: v1 17 | kind: Service 18 | metadata: 19 | name: tornjak-backend-http 20 | namespace: spire 21 | spec: 22 | type: NodePort 23 | ports: 24 | - name: tornjak-backend-http 25 | port: 10000 26 | targetPort: 10000 27 | protocol: TCP 28 | selector: 29 | app: spire-server 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: tornjak-backend-tls 35 | namespace: spire 36 | spec: 37 | type: NodePort 38 | ports: 39 | - name: tornjak-backend-tls 40 | port: 20000 41 | targetPort: 20000 42 | protocol: TCP 43 | selector: 44 | app: spire-server 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | name: tornjak-backend-mtls 50 | namespace: spire 51 | spec: 52 | type: NodePort 53 | ports: 54 | - name: tornjak-backend-mtls 55 | port: 30000 56 | targetPort: 30000 57 | protocol: TCP 58 | selector: 59 | app: spire-server 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | namespace: spire 65 | name: tornjak-frontend 66 | spec: 67 | type: LoadBalancer 68 | selector: 69 | app: spire-server 70 | ports: 71 | - name: tornjak-frontend 72 | port: 3000 73 | targetPort: 3000 74 | -------------------------------------------------------------------------------- /docs/quickstart/server-statefulset-examples/backend-sidecar-server-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | labels: 7 | app: spire-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: spire-server 13 | serviceName: spire-server 14 | template: 15 | metadata: 16 | namespace: spire 17 | labels: 18 | app: spire-server 19 | spec: 20 | serviceAccountName: spire-server 21 | containers: 22 | - name: spire-server 23 | image: ghcr.io/spiffe/spire-server:1.10.4 24 | args: 25 | - -config 26 | - /run/spire/config/server.conf 27 | ports: 28 | - containerPort: 8081 29 | volumeMounts: 30 | - name: spire-config 31 | mountPath: /run/spire/config 32 | readOnly: true 33 | - name: spire-data 34 | mountPath: /run/spire/data 35 | readOnly: false 36 | - name: socket # 👈 ADDITIONAL VOLUME 37 | mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME 38 | livenessProbe: 39 | httpGet: 40 | path: /live 41 | port: 8080 42 | failureThreshold: 2 43 | initialDelaySeconds: 15 44 | periodSeconds: 60 45 | timeoutSeconds: 3 46 | readinessProbe: 47 | httpGet: 48 | path: /ready 49 | port: 8080 50 | initialDelaySeconds: 5 51 | periodSeconds: 5 52 | ### 👈 BEGIN ADDITIONAL CONTAINER ### 53 | - name: tornjak-backend 54 | image: ghcr.io/spiffe/tornjak-backend:v2.0.0 55 | args: 56 | - --spire-config 57 | - /run/spire/config/server.conf 58 | - --tornjak-config 59 | - /run/spire/tornjak-config/server.conf 60 | volumeMounts: 61 | - name: spire-config 62 | mountPath: /run/spire/config 63 | readOnly: true 64 | - name: tornjak-config 65 | mountPath: /run/spire/tornjak-config 66 | readOnly: true 67 | - name: spire-data 68 | mountPath: /run/spire/data 69 | readOnly: false 70 | - name: socket 71 | mountPath: /tmp/spire-server/private 72 | ### 👈 END ADDITIONAL CONTAINER ### 73 | volumes: 74 | - name: spire-config 75 | configMap: 76 | name: spire-server 77 | - name: tornjak-config # 👈 ADDITIONAL VOLUME 78 | configMap: # 👈 ADDITIONAL VOLUME 79 | name: tornjak-agent # 👈 ADDITIONAL VOLUME 80 | - name: socket # 👈 ADDITIONAL VOLUME 81 | emptyDir: {} # 👈 ADDITIONAL VOLUME 82 | volumeClaimTemplates: 83 | - metadata: 84 | name: spire-data 85 | namespace: spire 86 | spec: 87 | accessModes: 88 | - ReadWriteOnce 89 | resources: 90 | requests: 91 | storage: 1Gi 92 | -------------------------------------------------------------------------------- /docs/quickstart/server-statefulset-examples/tornjak-sidecar-server-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | labels: 7 | app: spire-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: spire-server 13 | serviceName: spire-server 14 | template: 15 | metadata: 16 | namespace: spire 17 | labels: 18 | app: spire-server 19 | spec: 20 | serviceAccountName: spire-server 21 | containers: 22 | - name: spire-server 23 | image: ghcr.io/spiffe/spire-server:1.4.4 24 | args: 25 | - -config 26 | - /run/spire/config/server.conf 27 | ports: 28 | - containerPort: 8081 29 | volumeMounts: 30 | - name: spire-config 31 | mountPath: /run/spire/config 32 | readOnly: true 33 | - name: spire-data 34 | mountPath: /run/spire/data 35 | readOnly: false 36 | - name: socket # 👈 ADDITIONAL VOLUME 37 | mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME 38 | livenessProbe: 39 | httpGet: 40 | path: /live 41 | port: 8080 42 | failureThreshold: 2 43 | initialDelaySeconds: 15 44 | periodSeconds: 60 45 | timeoutSeconds: 3 46 | readinessProbe: 47 | httpGet: 48 | path: /ready 49 | port: 8080 50 | initialDelaySeconds: 5 51 | periodSeconds: 5 52 | ### 👈 BEGIN ADDITIONAL CONTAINER ### 53 | - name: tornjak 54 | image: ghcr.io/spiffe/tornjak:v1.2.2 55 | imagePullPolicy: Always 56 | args: 57 | - --spire-config 58 | - /run/spire/config/server.conf 59 | - --tornjak-config 60 | - /run/spire/tornjak-config/server.conf 61 | env: 62 | - name: REACT_APP_API_SERVER_URI 63 | value: http://localhost:10000 64 | - name: NODE_OPTIONS 65 | value: --openssl-legacy-provider 66 | ports: 67 | - containerPort: 8081 68 | volumeMounts: 69 | - name: spire-config 70 | mountPath: /run/spire/config 71 | readOnly: true 72 | - name: tornjak-config 73 | mountPath: /run/spire/tornjak-config 74 | readOnly: true 75 | - name: spire-data 76 | mountPath: /run/spire/data 77 | readOnly: false 78 | - name: socket 79 | mountPath: /tmp/spire-server/private 80 | ### 👈 END ADDITIONAL CONTAINER ### 81 | volumes: 82 | - name: spire-config 83 | configMap: 84 | name: spire-server 85 | - name: tornjak-config # 👈 ADDITIONAL VOLUME 86 | configMap: # 👈 ADDITIONAL VOLUME 87 | name: tornjak-agent # 👈 ADDITIONAL VOLUME 88 | - name: socket # 👈 ADDITIONAL VOLUME 89 | emptyDir: {} # 👈 ADDITIONAL VOLUME 90 | volumeClaimTemplates: 91 | - metadata: 92 | name: spire-data 93 | namespace: spire 94 | spec: 95 | accessModes: 96 | - ReadWriteOnce 97 | resources: 98 | requests: 99 | storage: 1Gi 100 | -------------------------------------------------------------------------------- /docs/quickstart/server-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | labels: 7 | app: spire-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: spire-server 13 | serviceName: spire-server 14 | template: 15 | metadata: 16 | namespace: spire 17 | labels: 18 | app: spire-server 19 | spec: 20 | serviceAccountName: spire-server 21 | containers: 22 | - name: spire-server 23 | image: ghcr.io/spiffe/spire-server:1.11.1 24 | args: 25 | - -config 26 | - /run/spire/config/server.conf 27 | ports: 28 | - containerPort: 8081 29 | volumeMounts: 30 | - name: spire-config 31 | mountPath: /run/spire/config 32 | readOnly: true 33 | - name: spire-data 34 | mountPath: /run/spire/data 35 | readOnly: false 36 | livenessProbe: 37 | httpGet: 38 | path: /live 39 | port: 8080 40 | failureThreshold: 2 41 | initialDelaySeconds: 15 42 | periodSeconds: 60 43 | timeoutSeconds: 3 44 | readinessProbe: 45 | httpGet: 46 | path: /ready 47 | port: 8080 48 | initialDelaySeconds: 5 49 | periodSeconds: 5 50 | volumes: 51 | - name: spire-config 52 | configMap: 53 | name: spire-server 54 | volumeClaimTemplates: 55 | - metadata: 56 | name: spire-data 57 | namespace: spire 58 | spec: 59 | accessModes: 60 | - ReadWriteOnce 61 | resources: 62 | requests: 63 | storage: 1Gi 64 | -------------------------------------------------------------------------------- /docs/quickstart/spire-bundle-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: spire-bundle 5 | namespace: spire -------------------------------------------------------------------------------- /docs/quickstart/spire-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: spire 5 | -------------------------------------------------------------------------------- /docs/quickstart/tornjak-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: tornjak-agent 5 | namespace: spire 6 | data: 7 | server.conf: | 8 | 9 | server { 10 | # location of SPIRE socket 11 | # here, set to default SPIRE socket path 12 | spire_socket_path = "unix:///tmp/spire-server/private/api.sock" 13 | 14 | # configure HTTP connection to Tornjak server 15 | http { 16 | port = 10000 # opens at port 10000 17 | } 18 | 19 | } 20 | 21 | plugins { 22 | DataStore "sql" { # local database plugin 23 | plugin_data { 24 | drivername = "sqlite3" 25 | filename = "/run/spire/data/tornjak.sqlite3" # stores locally in this file 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /docs/rsrc/agent-extended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/agent-extended.png -------------------------------------------------------------------------------- /docs/rsrc/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/agent.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/10-realm-import-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/10-realm-import-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/11-imported-realm-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/11-imported-realm-json.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/12-imported-resources-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/12-imported-resources-window.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/13-add-redirect-uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/13-add-redirect-uri.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/14-invalid-redirect-uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/14-invalid-redirect-uri.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/15-ingress-for-redirect-uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/15-ingress-for-redirect-uri.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/16-login-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/16-login-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/17-import-client-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/17-import-client-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/18-imported-client-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/18-imported-client-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/19-create-client-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/19-create-client-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/20-client-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/20-client-general.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/21-client-capability-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/21-client-capability-config.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/22-client-settings-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/22-client-settings-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/23-client-access-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/23-client-access-settings.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/24-client-scopes-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/24-client-scopes-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/25-full-scope-allowed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/25-full-scope-allowed.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/26-realm-roles-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/26-realm-roles-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/27-realm-create-role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/27-realm-create-role.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/28-realm-created-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/28-realm-created-roles.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/29-client-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/29-client-roles.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/30-client-created-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/30-client-created-roles.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/31-client-associated-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/31-client-associated-roles.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/32-list-of-available-realm-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/32-list-of-available-realm-roles.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/33-create-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/33-create-group.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/34-role-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/34-role-mapping.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/35-created-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/35-created-groups.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/36-realm-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/36-realm-settings.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/37-user-registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/37-user-registration.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/38-create-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/38-create-user.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/39-select-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/39-select-group.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/4-server-access-ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/4-server-access-ui.jpg -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/40-created-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/40-created-users.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/41-user-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/41-user-credentials.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/42-assign-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/42-assign-password.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/5-server-login-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/5-server-login-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/6-admin-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/6-admin-console.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/7-create-realm-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/7-create-realm-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/8-realm-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/8-realm-page.png -------------------------------------------------------------------------------- /docs/rsrc/keycloak_diagrams/9-realm-settings-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/keycloak_diagrams/9-realm-settings-page.png -------------------------------------------------------------------------------- /docs/rsrc/mac-system-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/mac-system-network.png -------------------------------------------------------------------------------- /docs/rsrc/mac-system-renewDHCP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/mac-system-renewDHCP.png -------------------------------------------------------------------------------- /docs/rsrc/mac-system-wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/mac-system-wifi.png -------------------------------------------------------------------------------- /docs/rsrc/manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/manager.png -------------------------------------------------------------------------------- /docs/rsrc/pkce-auth-code-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/pkce-auth-code-flow.png -------------------------------------------------------------------------------- /docs/rsrc/standard-auth-code-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/standard-auth-code-flow.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-agent-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-agent-browser.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-agent-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-agent-list.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-backend-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-backend-diagram.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-backend-plugin-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-backend-plugin-diagram.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-create-entries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-create-entries.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-create-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-create-token.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-entries-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-entries-list.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-manage-servers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-manage-servers.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-serverInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-serverInfo.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-ui-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-ui-diagram.png -------------------------------------------------------------------------------- /docs/rsrc/tornjak-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/tornjak-ui.png -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Agent list.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Agent list.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Create Entries.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Create Entries.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Create Token.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Create Token.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Entries List.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Entries List.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-High Level Back End Box Diagram.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-High Level Back End Box Diagram.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-High Level UI Box Diagram.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-High Level UI Box Diagram.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Manage Servers.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Manage Servers.vsdx -------------------------------------------------------------------------------- /docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Tornjak ServerInfo.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/docs/rsrc/vsdx diagrams/Tornjak UI API Diagram-Tornjak ServerInfo.vsdx -------------------------------------------------------------------------------- /docs/tornjak-agent.md: -------------------------------------------------------------------------------- 1 | # Tornjak Agent 2 | 3 | ![tornjak-agent](rsrc/agent.png) 4 | 5 | The components of tornjak on the SPIRE server should ideally be minimal. It should leverage the APIs of the SPIRE server as well as all the controls based around the SPIRE server, this leads to enforcement point being as close to the action as well as not having to have additional management throughout the stack. However, in order to fulfill some of the capabilities that are needed today and current state of features being developed, the component will take up a bigger role, this includes processing of outputs from the SPIRE api as well as storing additional metadata to provide additional functionality. 6 | 7 | As certain new features are built out in SPIRE, these responsibility can then be transfered to use SPIRE native mechanisms. In addition, if certain additional APIs will have a corresponding API in the future, then the capabilities will move to using the SPIRE APIs. 8 | 9 | ## Tornjak SPIRE Server Agent 10 | 11 | - Provides “proxy” to SPIRE API services 12 | - Provides additional APIs that are useful to management 13 | - Ideally all useful information will be part of the SPIRE API, calling through Tornjak SPIRE backend, but because additional information is required and components are still not yet in place to enable direct pass through for authentication (issue #TBD) and authorization yet (issue #1975), some functionality is implemented as part of the Tornjak agent server with its own data storage. 14 | 15 | As the SPIRE and Tornjak server gets integrated into more infrastructure 16 | services, the architecture would look a bit more like this: 17 | 18 | ![tornjak-agent](rsrc/agent-extended.png) 19 | 20 | ### APIs 21 | 22 | - SPIRE specific 23 | - /api/spire/agent/* 24 | - /api/spire/entry/* 25 | - etc. 26 | - Example additional APIs 27 | - /api/tornjak/* 28 | - e.g. 29 | - /api/tornjak/getserverinfo 30 | - /api/tornjak/getlogs 31 | - etc. 32 | 33 | ### Authentication 34 | 35 | - Ideally, authentication should be handled through SPIRE server, today, this is done via the socket or via the "Admin" flag for a SPIFFE ID within the trust domain. There are conversations about this [#2099](https://github.com/spiffe/spire/issues/2099) to enable SPIFFE IDs outside the trust domain of the SPIRE server or through other authentication mechanisms to administer the SPIRE server. This is to address the bootstrapping problem of administration of a SPIRE server. 36 | - In the meantime, the Tornjak agent will use the socket to authenticate with the SPIRE server. This means that additional authentication needs to be done to authenticate the caller (management plane) to the agent. For this, we will implement standard client authentication mechanisms configured on the agent. This will mirror the configuration of our ideal state with a workaround for authentication to be done. 37 | 38 | ### Authorization 39 | 40 | - Ideally, authentication should be handled through SPIRE server mechanisms, with the feature from [#1975](https://github.com/spiffe/spire/issues/1975) 41 | - For now, we will have a flat access model, which is equivalent to "Admin" flag of the SPIRE server. 42 | -------------------------------------------------------------------------------- /docs/tornjak-manager.md: -------------------------------------------------------------------------------- 1 | # Tornjak manager 2 | 3 | ![tornjak-manager](rsrc/manager.png) 4 | 5 | The tornjak manager provides a way to manage identities, providing capabilities such as: 6 | 7 | - Global visibility of identity registrations 8 | - Management of SPIRE identity configurations (Q) 9 | - Identity policy management 10 | - Auditability of identities 11 | 12 | The design idea behind the manager is to act as a thin layer between the user and the tornjak agents, and relevant policy/logging components. 13 | 14 | ## Global visibility 15 | 16 | Provide a dashboard of identity information and api calls to various spire servers through the tornjak agent. 17 | 18 | Actions possible: 19 | 20 | - Register tornjak agent / spire server 21 | - Provide address 22 | - Choose authentication method - User or Certificate [pending auth issue #TBD] 23 | - Engineering detail, for user, the credentials from the web browser of the user would be used to authenticate, if certificates, certificates will be provided and the backend would be responsible for authentication. i.e. AJAX/axios calls would be "..." vs "/address/api" for user vs server 24 | - Server + optional credentials are stored in the manager DB 25 | - SPIRE API actions on servers 26 | - frontend/backend for all SPIRE API actions 27 | - Get server information + custom tornjak APIs 28 | - This will include what are the plugins used in the server and pointing to logging and policy configurations for linking audit info. centrally 29 | - frontend/backend for all custom tornjak API actions 30 | 31 | ## Identity policy management 32 | 33 | - Provide an interface to the policy engines used by the SPIRE deployments 34 | - Actions 35 | - Add policy engine 36 | - Provide address 37 | - Choose authentication method - User or Certificate [pending auth issue #TBD] 38 | - Visibility and consumability of policies 39 | - Provide templates for more complex policies to be created 40 | - CRUD on policies 41 | 42 | ## Auditability of Identities and use for operations/forensics 43 | 44 | - Provide a collection of information, including logs, policy and registrations. This will include the statistics of use of SVIDs, minting, etc. 45 | - May depend on additional features in SPIRE required 46 | - Provide a way to consume this information or export the feed to another log consuming /metrics frontend. 47 | 48 | ## Management of SPIRE Identity configurations 49 | 50 | Provide the ability to configure identities, including the ability to configure attestation configs, plugins, etc. 51 | 52 | Actions possible: 53 | 54 | - Ability to set identity related configuration that is not exposed by the SPIRE API today, i.e.: 55 | - Add a node attestor to a node 56 | - Set workload registrar configuration 57 | - These set of actions are tricky, which begs the question of whether some of these actions should be exposed via the SPIRE server API. 58 | - These configurations are done today on the SPIRE server configuration file. Therefore it requires the management of the deployment in order 59 | for these configurations to be managed for it. This raises some questions to be discussed about how to manage plugins. 60 | - It is not straight forward for these to be managed by the tornjak agent because sometimes, these configurations are backed by non-traditional storage. 61 | i.e. kubernetes configmap. In these cases, in order for the agent to be effective, it needs to be configured with knowledge of how to persist changes. 62 | -------------------------------------------------------------------------------- /examples/docker-compose/Dockerfile.add-keycloak: -------------------------------------------------------------------------------- 1 | FROM quay.io/keycloak/keycloak:19.0.1 2 | # copy realm json file to container 3 | COPY examples/keycloak/realm /opt/keycloak/data/import/ 4 | # import realm from dir 5 | RUN /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import/ --override true; exit 0 6 | # start the image 7 | ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "-v", "start-dev"] -------------------------------------------------------------------------------- /examples/docker-compose/docker-compose-frontend.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | tornjak-frontend: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile.frontend-container 7 | container_name: tornjak-frontend 8 | restart: always 9 | ports: 10 | - "3000:8080" 11 | environment: 12 | - "PORT_FE=8080" 13 | - "REACT_APP_API_SERVER_URI=http://localhost:10000" 14 | # - "REACT_APP_AUTH_SERVER_URI=http://localhost:8080" 15 | - REACT_APP_SPIRE_HEALTH_CHECK_ENABLE=false 16 | -------------------------------------------------------------------------------- /examples/docker-compose/docker-compose-keycloak.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | keycloak: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile.add-keycloak 7 | container_name: tornjak-keycloak 8 | restart: always 9 | ports: 10 | - "8080:8080" 11 | environment: 12 | KEYCLOAK_ADMIN: admin 13 | KEYCLOAK_ADMIN_PASSWORD: admin 14 | volumes: 15 | - ./examples/keycloak/realm:/opt/keycloak/data/import -------------------------------------------------------------------------------- /examples/docker-compose/frontend.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | tornjak-frontend: 4 | build: 5 | context: ../../ 6 | dockerfile: Dockerfile.frontend-container 7 | container_name: tornjak-frontend 8 | restart: always 9 | ports: 10 | - "3000:8080" 11 | environment: 12 | - "PORT_FE=8080" 13 | - "REACT_APP_API_SERVER_URI=http://localhost:10000" 14 | # - "REACT_APP_AUTH_SERVER_URI=http://localhost:8080" 15 | - REACT_APP_SPIRE_HEALTH_CHECK_ENABLE=false 16 | -------------------------------------------------------------------------------- /examples/keycloak/README.md: -------------------------------------------------------------------------------- 1 | # Deployment of Keycloak to Support Tornjak User Management 2 | 3 | ## Deployment 4 | 5 | Deploy the Keycloak instance to support Identity and Access Management (IAM) for Tornjak. 6 | 7 | ```sh 8 | kubectl create -f config.yaml 9 | kubectl create -f statefulset.yaml 10 | kubectl create -f service.yaml 11 | ``` 12 | 13 | Once the service is deployed, provide a local access to Keycloak service port: 14 | 15 | ```sh 16 | kubectl port-forward service/keycloak-service 8080:8080 17 | ``` 18 | 19 | Then access Keycloak via browser on [http://localhost:8080](http://localhost:8080) 20 | and open the *Administration Console* 21 | 22 | The credentials in this example have username and password both `admin`. You may configure this in `statefulset.yaml` 23 | 24 | The Tornjak Realm has two users with usernames: `admin` and `viewer`, and passwords `admin` and `viewer` respectively. 25 | -------------------------------------------------------------------------------- /examples/keycloak/client/tornjak-keycloak-client-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": "Tornjak-React-auth", 3 | "name": "Tornjak", 4 | "description": "", 5 | "rootUrl": "", 6 | "adminUrl": "", 7 | "baseUrl": "", 8 | "surrogateAuthRequired": false, 9 | "enabled": true, 10 | "alwaysDisplayInConsole": true, 11 | "clientAuthenticatorType": "client-secret", 12 | "redirectUris": [ 13 | "http://localhost:3000/*" 14 | ], 15 | "webOrigins": [ 16 | "*" 17 | ], 18 | "notBefore": 0, 19 | "bearerOnly": false, 20 | "consentRequired": false, 21 | "standardFlowEnabled": true, 22 | "implicitFlowEnabled": false, 23 | "directAccessGrantsEnabled": false, 24 | "serviceAccountsEnabled": false, 25 | "publicClient": true, 26 | "frontchannelLogout": true, 27 | "protocol": "openid-connect", 28 | "attributes": { 29 | "oidc.ciba.grant.enabled": "false", 30 | "backchannel.logout.session.required": "true", 31 | "post.logout.redirect.uris": "http://localhost:3000/*", 32 | "display.on.consent.screen": "false", 33 | "oauth2.device.authorization.grant.enabled": "false", 34 | "backchannel.logout.revoke.offline.tokens": "false" 35 | }, 36 | "authenticationFlowBindingOverrides": {}, 37 | "fullScopeAllowed": true, 38 | "nodeReRegistrationTimeout": -1, 39 | "protocolMappers": [ 40 | { 41 | "name": "tornjak-backend-aud", 42 | "protocol": "openid-connect", 43 | "protocolMapper": "oidc-audience-mapper", 44 | "consentRequired": false, 45 | "config": { 46 | "id.token.claim": "false", 47 | "access.token.claim": "true", 48 | "included.custom.audience": "tornjak-backend", 49 | "userinfo.token.claim": "false" 50 | } 51 | } 52 | ], 53 | "defaultClientScopes": [ 54 | "web-origins", 55 | "acr", 56 | "profile", 57 | "roles", 58 | "email" 59 | ], 60 | "optionalClientScopes": [ 61 | "address", 62 | "phone", 63 | "offline_access", 64 | "microprofile-jwt" 65 | ], 66 | "access": { 67 | "view": true, 68 | "configure": true, 69 | "manage": true 70 | } 71 | } -------------------------------------------------------------------------------- /examples/keycloak/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: keycloak-service 5 | spec: 6 | type: NodePort 7 | ports: 8 | - name: keyclaok 9 | port: 8080 10 | targetPort: 8080 11 | protocol: TCP 12 | selector: 13 | app: keycloak 14 | -------------------------------------------------------------------------------- /examples/keycloak/statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: keycloak-for-tornjak 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: keycloak 10 | serviceName: keycloak-service 11 | template: 12 | metadata: 13 | labels: 14 | app: keycloak 15 | 16 | spec: 17 | containers: 18 | - name: keycloak 19 | image: quay.io/keycloak/keycloak:19.0.1 20 | imagePullPolicy: Always 21 | command: 22 | - /bin/sh 23 | - -c 24 | - | 25 | /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import/ --override true 26 | /opt/keycloak/bin/kc.sh -v start-dev 27 | ports: 28 | - name: http 29 | containerPort: 8080 30 | protocol: TCP 31 | env: 32 | - name: KEYCLOAK_ADMIN 33 | value: admin 34 | - name: KEYCLOAK_ADMIN_PASSWORD 35 | value: admin 36 | volumeMounts: 37 | - name: keycloak-config 38 | mountPath: /opt/keycloak/data/import 39 | volumes: 40 | - name: keycloak-config 41 | configMap: 42 | name: keycloak-config 43 | -------------------------------------------------------------------------------- /examples/multiple_entries_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": [ 3 | { 4 | "spiffe_id": { 5 | "path": "/sample_path1" 6 | }, 7 | "selectors": [ 8 | { 9 | "type": "k8s", 10 | "value": "ns:default" 11 | }, 12 | { 13 | "type": "k8s", 14 | "value": "container-image:ubuntu" 15 | } 16 | ], 17 | "admin": false 18 | }, 19 | 20 | { 21 | "spiffe_id": { 22 | "path": "/sample_path2" 23 | }, 24 | "selectors": [ 25 | { 26 | "type": "k8s", 27 | "value": "sa:operator" 28 | }, 29 | { 30 | "type": "k8s", 31 | "value": "container-name:my-operator" 32 | } 33 | ], 34 | "admin": true, 35 | "ttl": 600, 36 | "downstream": true 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /examples/registrar_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": [ 3 | { 4 | "spiffe_id": { 5 | "path": "/registrar1" 6 | }, 7 | "selectors": [ 8 | { 9 | "type": "k8s", 10 | "value": "ns:spire" 11 | }, 12 | { 13 | "type": "k8s", 14 | "value": "sa:spire-k8s-registrar" 15 | }, 16 | { 17 | "type": "k8s", 18 | "value": "container-name:k8s-workload-registrar" 19 | } 20 | ], 21 | "admin": true 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /examples/tls_mtls/CA-client/rootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /examples/tls_mtls/CA-client/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /examples/tls_mtls/CA-client/rootCA.srl: -------------------------------------------------------------------------------- 1 | 028D2EDD44518926CB4B77030BC4A45D38B781BF 2 | -------------------------------------------------------------------------------- /examples/tls_mtls/CA-server/rootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /examples/tls_mtls/CA-server/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKQIBAAKCAgEAs2H1wCiHNRIYNPoGv7Vg35wGKX9mdkdPqWy0vktXR2fbH8MX 3 | O9mTVIu8+oz/XzJC3YQlzjK67OdFSZdg7NBhZ9n4YCMwISfzCjn90s72bTIMa5rZ 4 | 9Zq4H4OAjDbIjjS1CLQ+a/hHP6yyevOWzCCsbAzAnjaMKdc0455RFYqYa7okMRU/ 5 | 3Qj4dQqa8BypKIrIKhV7/SgC5xy70ukJSFNiH3/qC5vqWu5LAWeX+54vQ6Sk5NuY 6 | qNjNgbrOIUieh/8LaKfpH5mzrAdPZB3whaTQWjs3pnxdoS76hQ7aLojYNYM13R5V 7 | X5QO2kfMlukzOgi7cbZkNqAKQ38mHF6TIOP7++C7ueVbP5DfADjj3KAAug4ghQm9 8 | viTPZ1x1++F0CTgEU2Sen4DSUZ+D2wanEiKbuyUphnq/sTC3aoBUbK4YxUI/HPwp 9 | 7zdyh7CJOB74JrbT7SZjyDy8Ygs6mC6Vi1rJb8ScqKMSgyQfzBD5JV/DCDHtnD9b 10 | mpYBq0BmSrLxb1IWZoc4Ts7KgTtlIn9mao+1cK5Yz2efuTcMlHDko7Rjd8MMuUjF 11 | 8PDORHyDCvxkp4/hmqy2tXtCsgfMxwunQuKs4DMsBOxEZ4fqecXATpAHgC2j5WyP 12 | 3MfFCLl4R8qimuxc+2E7CDu4StdNBNkY2oEvVfdfaNMsbHTREALXOMfJQoMCAwEA 13 | AQKCAgA64uuTuzo3d8suiO1yPY6hmUyEjugJ5/sEuUTUO1NZg4Rxds/Hu/MbjAvr 14 | jCHBFHTS1zC58flfD8S1Fdahpv5y4yEgHi0MlVq5frQOYhaPXiCpqlnmwHW1Eqw+ 15 | WzWXSUIsq07ajtSE2KJ2rkRQmmE/zyfkSC/XBGi+WBhyI99Jbf+3hSxD2VAGzAZ0 16 | 0FPPlU+EUS00u8/IIqyd3hZIAedyQ8GHMhXK5/MDcQh5d0I4yHHBFz2UICfGcJZ+ 17 | YqJ4LoeGQajL0N8kl/m9+f4dQS+Mj2gDZUwvP4bJ60hAgnfHiV1DS1PnXmlJyswk 18 | tkU0xe0StTdMn1j/M1xFb96Iq47qYe9jMooBJ7WLti7JaYTkPHFgCzsMm+eGwsBk 19 | TFTF+8SwKPQXDu75F4oZW/xHqFbiw0DCIsf9CtCl4Exe/hSFbWtm6N9Ui8LRYBts 20 | tJIchg2SjhUMYL3z1gJtJ8nTedw3tPjCkU/EuNdhMFUiV1cS7rz/tfyJoELnvuk2 21 | xVXmuaqjWG6kReXqTTthjjF62zlEVKqAClllzu/0MtXiYtpvsXN59kcPU4r/0nPm 22 | VGWl/Vi4UAqKyMnwwi1/k2F0dfiMtKyvo2Qbrb4P4NNUF54P4Jmy4M7LU84FZRcZ 23 | HT/2UFb8RrPAghxO4UmlXlBC93mynS7tAb83tZmUiUZRGBddsQKCAQEA2vz+v5Hr 24 | IQUEWUqupPqkf/1KbaX2yXNG4xto+5iZHDVl210cQaeY3kjdJ/OyFryIqSL2j1q9 25 | mg7cgWXq+nqzFaZzce3hKSjcADRz758Hgz60XJ8TKmo3VAKoqYvEqhSSQMW4V2lI 26 | rpSSj5mXrsjw+hjg23ju3FnncErdTh0hGaWBVveyOzDun0TRnJlAI5Sk/eMtb+wn 27 | NYMkGpP8o4ExDHzWBoAkIuan+GLnAO2g12Rll6lUzxYREkCOLralpIdGa7LnOrh0 28 | XB89xE1igPUOIY7arH3pzAbMfFO6NvT1cE2LTCRufMlGSzLcnc0+adrkGB55h69U 29 | a7PJDE6Ubl+G9QKCAQEA0bNXhzP5b14ANT46CvD6VnQvfRgjOzr3uk9UFcxeqGzn 30 | I32goCtEzXjzwSvMs+gDaNNzS6JCKVT9dN+2YtPWar9/V5CYDNHhJyW1BoARInh9 31 | ZzlERPXMVNs6DcvWZqY8ASge5F8qyuJHbk6NqXCUeLrt2t/wR3Cx91z42KX+NxeR 32 | T1tskZMlDUqq99nKSsLeUwRR4CvkubdI8PrwmCsXzsZkp22AJG6akjexFG4GnRaw 33 | CjCjGh1U6/QzVNMCxhhunja9BfZ7nAG2rRfDWi2bBS+JL3l+oCaNsA9GWAzknkpz 34 | XsqxYRxFSKzFijcm0MJwsgrEZuzz/rkPbllNbrUIlwKCAQEAp7UFK5UGax06fV+S 35 | bEp/XH2QWHS3kQO1kAvX1IbDCzVhsiOWljlR+zn2FLiu9HaielWKWbL2bVtgR8DT 36 | ucvqBnSZCPFSdIEUKxwAhcxWpo6I9j3lIaCCitYWcPHXRSHH9870JA0/WmPx8gOA 37 | hJzi38XZvnrZAuvfMMJExaPNS6TSbtX3KxmIRXUgyjsbQaR+zukCTSqfxH2QUsJu 38 | wpEYDSY1injsM8ZNcQ3dN3rEqO/8Va+agZW2sTG7Mc8y/9ORfL1gFDpa8ooCdamF 39 | /Jivn8eVdHBhcpzqUXSVsazboovgWAm2i95g9QoYpskeOVcnQ8li7SS7FFh1afVr 40 | +zMxWQKCAQA7K1t7LtMm9WMhotlF1REN6+KqbXEjeVQFIfUb4XqkE3Z0sTHtf8f+ 41 | Ii7FRtKPwNuVbHSP1szMVMrfe3A/Gn7ZyAbqckY099OL9DvMBb7P9yeLRMFao/Bv 42 | 0RvkYKuycx1jRirRTXkJnieBnHCkaru6BSSwKZfm0ImmvnkIyG2epeu+dJGB0f8W 43 | bQcowC8wQSPyULm5gykarfjK5kOU7DPfiGNGSsNphDvhHg29Mf5zvL8XYPnkmHop 44 | ydHiEVgrBSHRSqizUgU209TzqkzY2R1wrSUMXD+6eKZqEyVKGvk3INLE4HSEbjGu 45 | e/GROladeP/LfPZGBH4kVZthC5mprzMpAoIBAQDEcceMeCaZwbIHV54ntBU/8u85 46 | nHITn2VaQtmTTJ+QbLI8TbG/YDCfnFY6CZMqn1tGr9fzP6wtgnnkIdrB4K5U0kNn 47 | 4LexzK2vwCBGoW5ngQnMRb4sd1w0gLKRVkndQaHBgQYxGpLmwX0j1Pib6/A2lIqh 48 | tDweq1y4DSIiDrt2nTt4P2C9D25UStFcmUFgDfXLkOaFfK4TwXToehdBc/Nrm3oU 49 | q9Vroqh3MuWA+PEyyJu7lZcZ6PcCW63hds9sfcji18CWPEeyas26Vm/7y9huZhmh 50 | DHpP5aTD8PJQmLEfGpoQM0ntGovE1Ft4hD9Fsiq3i1E+mxsgslZVEs3juG2C 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /examples/tls_mtls/CA-server/rootCA.srl: -------------------------------------------------------------------------------- 1 | 7A092E3ECC3F16AA095724B3FFD2EB246C17B157 2 | -------------------------------------------------------------------------------- /examples/tls_mtls/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIETDCCAjSgAwIBAgIUAo0u3URRiSbLS3cDC8SkXTi3gb8wDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApBY21lLCBJ 4 | bmMuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yMzA2MTIxNjExMDJaFw0yNDEw 5 | MjQxNjExMDJaMEcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UECgwL 6 | TXlPcmcsIEluYy4xFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCASIwDQYJKoZIhvcN 7 | AQEBBQADggEPADCCAQoCggEBANMLlsBrmolGbWYO63ljSKYrSpoqik+ImJ3+4oSi 8 | lrJXeSormENTI1yBvH8nB1FuwAz9mGRewb9n26P7ts4Y+AQDn8I5TCIpoWblnG2b 9 | iNu/i4ygWbZwxNdQ1gUCW4P4IPyBYPRJo8HtGCM4feUmEpTQtsBbsjUer/MYEW4n 10 | KatdEwYTeq36CJZHjgQ5vnyIVrnI6lyf8f6g+5Y8MSrYrI2CDqaG9q5BRvfQpaCn 11 | RkZ00/4Z6BzHHu8N3iPJX2eSKM8MaK6Q893KbEJ+FOjV024XPVwhsbRo2d78lv2C 12 | fxCCK6d7zCeO0EAjdCReMNACH6jxqJLDXTfgb7NDZn2dcr8CAwEAAaMyMDAwLgYD 13 | VR0RBCcwJYIFbG9jYWyCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wDQYJ 14 | KoZIhvcNAQELBQADggIBAFmLVa1/LyKNO1CT2iSdNEDAGous6EgAW5PBDDdz9Tkv 15 | yzgX/wetczvHip7vspvajYVKHJJR2MNc6HegewXXqU+3Oskd+hbpT4uR7ha2CQPo 16 | oTqnP4F0ewuA9OS33uf9xTpeWmm6UcoxtfhI0dxEb2CRVbMXVS4vGB5pBY/GFRbs 17 | wo0HYiWco7+6tUlN5HeG4OfhkQIyp9CiDJwZ266/bHbjMZPcbxUdswXbAHXhI5cd 18 | vqHGrlBlBLsrggRLDu+ROe5txJEfp95FDPufuOwyGdLf17hPhmCO78bRUE0a3B1X 19 | nKGoL7lb8sFhVkq3h4sSLnZOoyA1k+7xtpQOpBE3TKLd+eIqrLVCFhiJ2mz/oahd 20 | MBU1dU2W+Ojgy1K3CCH2Ly4lV7OqiY3cCBXueux8BY0Hae35iytp/i+wQzypYYdo 21 | ZEmNsX8hckJ0uhLT7TcDCLFFaa8v6xlLBIlYKKXL6x3e0hkvS6WNvSApnMqBwpay 22 | N8vp1+nCQ3DToPFZdoNokVQPGWW/BGl5mR2O5ps8g7ZKK/f06G4HUxCdaxU2iGnP 23 | KVMLius4E5jK4AlbA0KjRy/tD+LtPOZ09+8g7AOx/inCFXdHDWbiyA23wAMDmrmM 24 | SO+pxZiTTU0HeZsz6Miq6YRGybIIMp2A4od/C+mfWkb4REhDoeKZKOeX1wydJcuw 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /examples/tls_mtls/client.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICzTCCAbUCAQAwRzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQK 3 | DAtNeU9yZywgSW5jLjEVMBMGA1UEAwwMbXlkb21haW4uY29tMIIBIjANBgkqhkiG 4 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0wuWwGuaiUZtZg7reWNIpitKmiqKT4iYnf7i 5 | hKKWsld5KiuYQ1MjXIG8fycHUW7ADP2YZF7Bv2fbo/u2zhj4BAOfwjlMIimhZuWc 6 | bZuI27+LjKBZtnDE11DWBQJbg/gg/IFg9Emjwe0YIzh95SYSlNC2wFuyNR6v8xgR 7 | bicpq10TBhN6rfoIlkeOBDm+fIhWucjqXJ/x/qD7ljwxKtisjYIOpob2rkFG99Cl 8 | oKdGRnTT/hnoHMce7w3eI8lfZ5IozwxorpDz3cpsQn4U6NXTbhc9XCGxtGjZ3vyW 9 | /YJ/EIIrp3vMJ47QQCN0JF4w0AIfqPGoksNdN+Bvs0NmfZ1yvwIDAQABoEEwPwYJ 10 | KoZIhvcNAQkOMTIwMDAuBgNVHREEJzAlggVsb2NhbIILZXhhbXBsZS5jb22CD3d3 11 | dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAMkagumVx+q9FOFKGwbhT 12 | G8o/Yn/rb545vJLKxEf6qzU4ftevJyYUJq4gAQjNwN+j1bt0C4t8CN16DoWzCDoA 13 | B7ilH7NN2RZb6UNpngE/1vGTs9zzkTQ9t9K5OII4UrOZMLCAV2OXqt76Qmwk2iSi 14 | I/CSeJHHUA9lEraiHok8kJ5ITmTwqgiskLHphEwTt+tBjOVg1g+8/xGb5lSkeP9U 15 | DE4DEX8CvLhabV1Gjb1txv6iNEoWtRmjduMS+Cww48aHXSJDtjb+9cwBAnuWxSTH 16 | /VhsS0W02lAd5BwUQkosuLVCINKZcB+nMdrwvz2KI2GCcecRQeX/Oz+tZyeD6NDw 17 | 4Q== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /examples/tls_mtls/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEA0wuWwGuaiUZtZg7reWNIpitKmiqKT4iYnf7ihKKWsld5KiuY 3 | Q1MjXIG8fycHUW7ADP2YZF7Bv2fbo/u2zhj4BAOfwjlMIimhZuWcbZuI27+LjKBZ 4 | tnDE11DWBQJbg/gg/IFg9Emjwe0YIzh95SYSlNC2wFuyNR6v8xgRbicpq10TBhN6 5 | rfoIlkeOBDm+fIhWucjqXJ/x/qD7ljwxKtisjYIOpob2rkFG99CloKdGRnTT/hno 6 | HMce7w3eI8lfZ5IozwxorpDz3cpsQn4U6NXTbhc9XCGxtGjZ3vyW/YJ/EIIrp3vM 7 | J47QQCN0JF4w0AIfqPGoksNdN+Bvs0NmfZ1yvwIDAQABAoIBAHdIy5JCv3vrlJXk 8 | xO89UKirO2VbugUuHaTflcSF6Usv6coODeevrALzSUlNE/PQ9zfgdiv06ul2mExd 9 | T3u53STXr4qlvARrJ1DzYrEJAhfCceuwDkTyBC/2/qCiLnuu2WYe8l/g53AKxGPT 10 | 4ESOel4mgcTDjzw69hQefGuYMxMpZumMuPzGScJSFQGqfpsCUwaezFdrLnNEkldW 11 | 5G4ktzSIUqiZQvKNvyNIs0brh3DhRf5FnvDiVc4aAnfp/6RyO6P+9J0asmoaJPTl 12 | uBJBnGCYzz6ikMa+1Z4Ktf95ZJpu8z1G99NTmEUHuQc+zMofFYtTM4SWnMRTI+nM 13 | dHf4sDECgYEA+H/+OjlV8f0+SrNMG38QoXOnG/J3n4hn5sV9DBAEPR5JvqoXbMiu 14 | yWhyXaJx8N361tbwL+QYPCqCK7btiyjRUitlntkLjI7LLD/CcPxzPViO+q8yqcF+ 15 | vISbepWytunaRb1Bp7QvdUPaNbu8z2YcZcHzjP7wQl4ucSpqHXErCT0CgYEA2Wo0 16 | zdUACxfzxi+bRMSYzE8XC8iUdCSt1vZAXqq5auDa6HAgrDDXxOqqKTMrUurUCL00 17 | fhT8TISbEDH5HwwqQmUoT8GmTbfgK6hDKOgMEa4pe6sHDtnUewGAK0bgr5AGcaCt 18 | k8zDqmAms6tLuXRR9LZ1OJ6Fpmf3oOyrdoxV06sCgYEAz8+JtPs5ynKzYxjp7pym 19 | Nb5X42EzdHBII47H8gx63vmzRgVMLabttHTqHy+4BWw9VujMV+Bx++64iQIjSJrL 20 | 4eF0zBBKPjMz6T8wxff4Dzc96poUzi2IZPKoay1BFQIfjO6mNy7R+UjS9NiZHwAP 21 | g3Fc0W5pUbcdM3n638BB8ykCgYEA1V/NiQD1dO3B+ox4ZypHB6TLam4lfEMPNXwi 22 | OoZ1SPZ7AUoiVrvs1z6zV5H3f/4lsJn4bZEs9+/guylAZH8s7lKXGIdmLro6UL5n 23 | gzsRtTxnTA7S83lHdp1Ha7G5C7RfDp9yGy8IDSsmcIi53b3SPUfgjXvOkT1SI2aC 24 | 9OrPxNcCgYEAnsE4WyDlN+FOJK4x4f3NJVcAjD2b2XQiL7xOXeSQN+P+oOQL8Kz0 25 | JikiWQ4SzQVXXVK4BcbfkuJrMqsVN4k/JPeUu/PXw0jf9O4sIprUeUbdUeeHpsbO 26 | sXJIePgCfwqML//bAA0uxg0za5uMyv8klvWVK7E27WE1aRGs0Srpmk0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/tls_mtls/create-ca.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage { 4 | echo "Takes in CA directory name (1)" 5 | exit 1 6 | } 7 | [[ -z $1 ]] && usage 8 | export CA_DIR=$1 9 | 10 | mkdir -p ${CA_DIR} 11 | nameCA="${CA_DIR}/rootCA" 12 | keyCA="$nameCA.key" 13 | # create key 14 | [[ ! -f "$keyCA" ]] && openssl genrsa -out "$keyCA" 4096 15 | 16 | # create certificate based on key 17 | # the CA subject is Acme Inc. Organization 18 | openssl req -x509 -subj "/C=US/ST=CA/O=Acme, Inc./CN=example.com" -new -nodes -key "$keyCA" -sha256 -days 3650 -out "$nameCA.crt" 19 | -------------------------------------------------------------------------------- /examples/tls_mtls/create-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | function usage { 3 | echo "Takes in domain name (1), cert name (2), CA directory (3) as input and outputs name.key and name.crt as output" 4 | exit 1 5 | } 6 | [[ -z $3 ]] && usage 7 | export DOMAIN=$1 8 | export CERTNAME=$2 9 | export CA_DIR=$3 10 | 11 | openssl genrsa -out ${CERTNAME}.key 2048 12 | openssl req -new -sha256 -key ${CERTNAME}.key -subj "/C=US/ST=CA/O=MyOrg, Inc./CN=mydomain.com" -out ${CERTNAME}.csr 13 | openssl req -new -sha256 -key ${CERTNAME}.key -subj "/C=US/ST=CA/O=MyOrg, Inc./CN=mydomain.com" -out ${CERTNAME}.csr \ 14 | -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:${DOMAIN},DNS:example.com,DNS:www.example.com")) 15 | openssl req -in ${CERTNAME}.csr -noout -text 16 | 17 | openssl x509 -req -extensions SAN \ 18 | -extfile <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:${DOMAIN},DNS:example.com,DNS:www.example.com")) \ 19 | -in ${CERTNAME}.csr -CA ${CA_DIR}/rootCA.crt -CAkey ${CA_DIR}/rootCA.key -CAcreateserial -out ${CERTNAME}.crt -days 500 -sha256 20 | openssl x509 -in ${CERTNAME}.crt -text -noout 21 | -------------------------------------------------------------------------------- /examples/tls_mtls/server-statefulset-tls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: spire-server 5 | namespace: spire 6 | labels: 7 | app: spire-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: spire-server 13 | serviceName: spire-server 14 | template: 15 | metadata: 16 | namespace: spire 17 | labels: 18 | app: spire-server 19 | spec: 20 | serviceAccountName: spire-server 21 | containers: 22 | - name: spire-server 23 | image: ghcr.io/spiffe/spire-server:1.4.4 24 | args: 25 | - -config 26 | - /run/spire/config/server.conf 27 | ports: 28 | - containerPort: 8081 29 | volumeMounts: 30 | - name: spire-config 31 | mountPath: /run/spire/config 32 | readOnly: true 33 | - name: spire-data 34 | mountPath: /run/spire/data 35 | readOnly: false 36 | - name: socket 37 | mountPath: /tmp/spire-server/private 38 | livenessProbe: 39 | httpGet: 40 | path: /live 41 | port: 8080 42 | failureThreshold: 2 43 | initialDelaySeconds: 15 44 | periodSeconds: 60 45 | timeoutSeconds: 3 46 | readinessProbe: 47 | httpGet: 48 | path: /ready 49 | port: 8080 50 | initialDelaySeconds: 5 51 | periodSeconds: 5 52 | - name: tornjak-backend 53 | image: docker.io/maiariyer/tornjak-backend:v1.3.0 54 | args: 55 | - --spire-config 56 | - /run/spire/config/server.conf 57 | - --tornjak-config 58 | - /run/spire/tornjak-config/server.conf 59 | ports: 60 | - containerPort: 8081 61 | volumeMounts: 62 | - name: spire-config 63 | mountPath: /run/spire/config 64 | readOnly: true 65 | - name: tornjak-config 66 | mountPath: /run/spire/tornjak-config 67 | readOnly: true 68 | - name: spire-data 69 | mountPath: /run/spire/data 70 | readOnly: false 71 | - name: socket 72 | mountPath: /tmp/spire-server/private 73 | - name: secret-volume # 👈 TLS SECRET VOLUME MOUNT 74 | mountPath: /opt/tornjak/server # 👈 TLS SECRET VOLUME MOUNT 75 | volumes: 76 | - name: spire-config 77 | configMap: 78 | name: spire-server 79 | - name: tornjak-config 80 | configMap: 81 | name: tornjak-agent 82 | - name: socket 83 | emptyDir: {} 84 | - name: secret-volume # 👈 TLS SECRET VOLUME 85 | secret: # 👈 TLS SECRET VOLUME 86 | secretName: tornjak-server-tls # 👈 TLS SECRET VOLUME 87 | volumeClaimTemplates: 88 | - metadata: 89 | name: spire-data 90 | namespace: spire 91 | spec: 92 | accessModes: 93 | - ReadWriteOnce 94 | resources: 95 | requests: 96 | storage: 1Gi 97 | -------------------------------------------------------------------------------- /examples/tls_mtls/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEUDCCAjigAwIBAgIUegkuPsw/FqoJVySz/9LrJGwXsVcwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApBY21lLCBJ 4 | bmMuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yMzA2MDYxNzQzMTJaFw0yNDEw 5 | MTgxNzQzMTJaMEcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UECgwL 6 | TXlPcmcsIEluYy4xFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCASIwDQYJKoZIhvcN 7 | AQEBBQADggEPADCCAQoCggEBAJfTn4UAf/VGTBii0QLtkREzNFcteDTiSZtyY+SA 8 | w/vIR1VgdKEb82Sztc5IoAGvXtzimHsQusvqt9u+wro/wPIw8UHlS66hO4zAxPij 9 | jYMy04mnACD7n+CHZeKFfbtsQT8AJKP9E57dHyshxETdbbDBSRZh5JuAsTt+AVeB 10 | IbJgjZORsS97ik288OBrzT0G76aa6x5NoYcfHg7+9NBoyOCD+iwIFIeqa6rxS805 11 | 24EtvB3wEAeU3rzFJHYM1dpwSVeuyQ/mt1o7xyZKADfEbBnRNdcfQnPcmV4150av 12 | nXMn35sfV5LPiNnYhNR8uw2+URgTutlaWyiBzptsDvXtfYsCAwEAAaM2MDQwMgYD 13 | VR0RBCswKYIJbG9jYWxob3N0ggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t 14 | MA0GCSqGSIb3DQEBCwUAA4ICAQCHUzs12fu74CE4g3vCaLCw3961TAvKpzfruvTc 15 | 7QYdVYF+LHRhfgJyGM/ouxlgwiXBNDDkbbe2O+xv9nbSNyYBo32JKbEtfxw3VWTq 16 | gzExFl5jge3hfI6n2l5k8qCrBRruQAgFzrfJ+avRdZycrZGnjaJiRowQfp9vEAZO 17 | ROU3f7Htflvboc5V1OB/5G29zz7TaE6mI6tv12ghZ0s55tJeYEK8hvtUmCjrd07X 18 | XU+kUAjSyd0/28L1bkbX+cTrZV4iLDG/qT/FXJ022JteNGxOm4oY5cKKIuDx8KSd 19 | oiQ/uiwwA0nndXsmLWoSHIT4UqvzCtnyrDwBrwszgIOjTmJ0NQMyspssGF8V6wRd 20 | Ki5dhGFLiZK5yKx6XwH/FATEaKKRQYiydZ1CeB4j62SvF32yc4PASgyVKLI3VZSf 21 | yNxJEGwsWZ2NoI2ZFRcYH7daFOCfF/mr/nXe2ZUczs9qYHRtedwy1tHOHPDtCHgb 22 | 5TxGatVjUId85Yj4iRcMJ6udRnsKeMrxL7sRCUVE55pRDaTn1z2K/gRSbW3Qdq3k 23 | 4vGCM/Yl32ImhXbcNHfMoA9ahfN4z9xRT/rYYq8jZnsNc+cqBvzW6yIs0iK+LBsp 24 | TzVbt9n9+vdrSrAtJ0z6Hw6NphHi0TJD7TNb0VFtCrC6daMElHff5pufUZ9DPvXA 25 | nezfXA== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /examples/tls_mtls/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC0TCCAbkCAQAwRzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQK 3 | DAtNeU9yZywgSW5jLjEVMBMGA1UEAwwMbXlkb21haW4uY29tMIIBIjANBgkqhkiG 4 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9OfhQB/9UZMGKLRAu2RETM0Vy14NOJJm3Jj 5 | 5IDD+8hHVWB0oRvzZLO1zkigAa9e3OKYexC6y+q3277Cuj/A8jDxQeVLrqE7jMDE 6 | +KONgzLTiacAIPuf4Idl4oV9u2xBPwAko/0Tnt0fKyHERN1tsMFJFmHkm4CxO34B 7 | V4EhsmCNk5GxL3uKTbzw4GvNPQbvpprrHk2hhx8eDv700GjI4IP6LAgUh6prqvFL 8 | zTnbgS28HfAQB5TevMUkdgzV2nBJV67JD+a3WjvHJkoAN8RsGdE11x9Cc9yZXjXn 9 | Rq+dcyffmx9Xks+I2diE1Hy7Db5RGBO62VpbKIHOm2wO9e19iwIDAQABoEUwQwYJ 10 | KoZIhvcNAQkOMTYwNDAyBgNVHREEKzApgglsb2NhbGhvc3SCC2V4YW1wbGUuY29t 11 | gg93d3cuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBABmVggorzkgJ8Ywe 12 | JVEoXNoqvrKiadsHtDGvGybCHdUwO0jjR+0uIuiSP11q9Ijrhff1N5Peq47C80yH 13 | KBeb4q4Be3RUtLCDDeK8LTLjJptdWwCoajnxSU9nzgSQdlJW/mRRSn/RBzRDa07Q 14 | f8y0uC2e4q9Pkp/U8oK1lv7DHK6SFXnfwwTIcxtXnj1jg4CST65t1QeQIicnkhtH 15 | kqBtwzatGejizDnCaIpbLrMEzf1LjJVTzD8Oy4Ewk96qqe4JTEq+06o5ots7GpkZ 16 | FWib3sgAoxzh5BxKlYXkC1Giu4wjdZRQAQz6UTE0LmIt0XLIo853qU61I+D7n2pJ 17 | PvNuFaE= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /examples/tls_mtls/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAl9OfhQB/9UZMGKLRAu2RETM0Vy14NOJJm3Jj5IDD+8hHVWB0 3 | oRvzZLO1zkigAa9e3OKYexC6y+q3277Cuj/A8jDxQeVLrqE7jMDE+KONgzLTiacA 4 | IPuf4Idl4oV9u2xBPwAko/0Tnt0fKyHERN1tsMFJFmHkm4CxO34BV4EhsmCNk5Gx 5 | L3uKTbzw4GvNPQbvpprrHk2hhx8eDv700GjI4IP6LAgUh6prqvFLzTnbgS28HfAQ 6 | B5TevMUkdgzV2nBJV67JD+a3WjvHJkoAN8RsGdE11x9Cc9yZXjXnRq+dcyffmx9X 7 | ks+I2diE1Hy7Db5RGBO62VpbKIHOm2wO9e19iwIDAQABAoIBAH5/qA1c2UA3DfgI 8 | 0DSsU44NJ7LrA5isYb/Ok0QztRb3S9fqwcHqU+S3hucUw5wjzdokQf9ndPup2P6R 9 | 9hTQQaaI+Lp6nZAmbkNb6cFwI9wIYQ/zwLu6av7cURtnNdcO313qdJzdo5ddjTaW 10 | r7zg8R+wK4Emlx6pHwH+foe146mGWFw7+gDu4FPgtlUFknwl3anKBEX+erVBskcT 11 | LAZd5Uq4bEOSuMZUV1msiVSBZAgqEBxNsczTF88Cqfa1rVwtW5lFQ/wPEsYEDicF 12 | M3C6GywGtVRSVGZC87dpRq0FRbE4S7nVALxHgp/HKWCd/vvfypHB95yV/VraCG1f 13 | bew8+GECgYEAyJ7hDzIWz2yJyj6e8KGtEhtYSr2PqYUTbH5OWjiFeyBedpFSES/v 14 | j6k9Psd/Axr8EnBIa1ecnBMNicdHd/uft+xuMZ+mmA2U2T3A1il7C5YAAtE8uca0 15 | hK9+3if/U5QQeqclxRTM9fYXlNQ1Rz3W5GUGpCTl2OwZWLyGgagGF40CgYEAwbyn 16 | YH/WLlSLv9s5FrvUdyJk0plTioJfOPp9F9HribhuUZu6eYU9HH1OZPhTyz7z1Qsc 17 | xBZSEQg4xZCZRw7KmwbrKjHhuV7iu4XXOOsF+nzLb4tL+tMKhDconS8136kN2DND 18 | d3GwI8yMLegNi1t9ZrqH+NZ8E3AmqKg7VRo+d3cCgYBIN2DeXnJ1kiV7htUh26D4 19 | Pp2msdvP8tZOHJ1JvsTV0I4QcjkvdKjwdsRUH+3piUIpxP9cnHoEeJtL+E8SsqII 20 | y/PSqxyF6YWmOaN7tAzV29X/LaCFYzDB/oZVo+I2DLtt21MuQVGSTFaqvUS8c1cy 21 | eedNGPcgAcUR+zMT4w7mVQKBgFLwCFdvYqmC2DDSEKaC0noN50uhB/qLxWgHE/ZW 22 | j5bttYyKNlI/j+HaYu0mwfiIgqn5j98CDy/rbSJmvLeGxYWIeLQflppc3IOa0IA1 23 | XOHW+uBNqcE/B4mCc4iW5WFSZfLgP0mrCdTJhFlCtkmqaQzJXdxRNdy8blBVdNEB 24 | 4ywRAoGAVDfQBAJJfal26P7GMyC9vgOjoTxiZuPVf7ruQj9xuTPIszQ1BERPRwIZ 25 | /16arBcWBMlzALwXsgpxxhDFAAVStl5lISd9vuFojYh3QroP4toA57Q9vqr+XbHI 26 | s3Xh+2U4NkLcQe84j/kbWchLWcKQ0YLuvrSsZQaMNSqc7vQw7Q0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/tls_mtls/tornjak-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: tornjak-agent 5 | namespace: spire 6 | data: 7 | server.conf: | 8 | 9 | server { 10 | # location of SPIRE socket 11 | # here, set to default SPIRE socket path 12 | spire_socket_path = "unix:///tmp/spire-server/private/api.sock" 13 | 14 | # configure HTTP connection to Tornjak server 15 | http { 16 | port = 10000 # opens at port 10000 17 | } 18 | 19 | https { 20 | port = 10443 21 | cert = "server/tls.crt" 22 | key = "server/tls.key" 23 | # client_ca = "users/rootCA.crt" 24 | } 25 | } 26 | 27 | plugins { 28 | DataStore "sql" { # local database plugin 29 | plugin_data { 30 | drivername = "sqlite3" 31 | filename = "/run/spire/data/tornjak.sqlite3" # stores locally in this file 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | ############### Default settings ############### 2 | # Note: Temporarily to suppress warrnings after reactscript upgarde, not a permanent fix 3 | GENERATE_SOURCEMAP=false 4 | NODE_PATH=src/ 5 | SKIP_PREFLIGHT_CHECK=true 6 | REACT_APP_DEBUG_TORNJAK=true 7 | REACT_APP_TORNJAK_MANAGER=false 8 | REACT_APP_API_VERSION=v1 9 | 10 | ##### Backend Server uri 11 | REACT_APP_API_SERVER_URI=http://localhost:10000/ 12 | 13 | ##### For user management ##### 14 | # REACT_APP_AUTH_SERVER_URI=http://localhost:8080/ 15 | REACT_APP_KEYCLOAK_REALM="tornjak" 16 | REACT_APP_OIDC_CLIENT_ID="tornjak" 17 | 18 | 19 | ##### To check SPIRE health ##### 20 | REACT_APP_SPIRE_HEALTH_CHECK_ENABLE=true -------------------------------------------------------------------------------- /frontend/.env.prod: -------------------------------------------------------------------------------- 1 | REACT_APP_DEBUG_TORNJAK=true 2 | REACT_APP_TORNJAK_MANAGER=false 3 | ##### Backend Server uri 4 | REACT_APP_API_SERVER_URI=http://localhost:10000/ 5 | 6 | ##### For user management ##### 7 | # REACT_APP_AUTH_SERVER_URI=http://localhost:8080/ 8 | REACT_APP_KEYCLOAK_REALM="tornjak" 9 | REACT_APP_OIDC_CLIENT_ID="tornjak" 10 | 11 | 12 | ##### To check SPIRE health ##### 13 | REACT_APP_SPIRE_HEALTH_CHECK_ENABLE=false 14 | -------------------------------------------------------------------------------- /frontend/.env.staging: -------------------------------------------------------------------------------- 1 | REACT_APP_TORNJAK_MANAGER=false 2 | REACT_APP_API_SERVER_URI=http://localhost:10000/ 3 | #REACT_APP_AUTH_SERVER_URI=http://localhost:8080/ 4 | ##### To check SPIRE health ##### 5 | REACT_APP_SPIRE_HEALTH_CHECK_ENABLE=false 6 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com/ 2 | save-prefix=^ 3 | shrinkwrap=true 4 | legacy-peer-deps=true -------------------------------------------------------------------------------- /frontend/Dockerfile.frontend-container: -------------------------------------------------------------------------------- 1 | ## Build stage 2 | FROM node:20-alpine AS build 3 | WORKDIR /usr/src/app 4 | COPY frontend . 5 | RUN npm install && \ 6 | npm run build 7 | 8 | ## Runtime stage 9 | FROM node:20-alpine AS runtime 10 | WORKDIR /usr/src/app 11 | COPY --from=build /usr/src/app/build ./build 12 | COPY --from=build /usr/src/app/.env.prod . 13 | 14 | # Install serve package and react-inject-env 15 | RUN npm install -g npm@10.7.0 && \ 16 | npm install --location=global serve && \ 17 | npm install react-inject-env 18 | RUN mkdir build/tmp 19 | 20 | # Set dynamic port, defualt 3000 21 | ENV PORT_FE=3000 22 | EXPOSE $PORT_FE 23 | 24 | # moving env.js to fix "access denied" error when running in restricted (read-only) env 25 | ENTRYPOINT npx react-inject-env set -n tmp/env.js && serve -s build -p $PORT_FE 26 | 27 | # add a version link to the image description 28 | ARG version 29 | ARG github_sha 30 | LABEL org.opencontainers.image.description="Tornjak frontend ($version) Alpine based image: https://github.com/spiffe/tornjak/releases/tag/$version" \ 31 | org.opencontainers.image.source="https://github.com/spiffe/tornjak" \ 32 | org.opencontainers.image.documentation="https://github.com/spiffe/tornjak/tree/main/docs" 33 | # additional labels 34 | LABEL architecture="amd64,arm64" \ 35 | build-date="" \ 36 | description="Tornjak Frontend" \ 37 | io.k8s.description="Tornjak Frontend" \ 38 | io.k8s.display-name="tornjak-frontend" \ 39 | maintainer="" \ 40 | name="spiffe/tornjak-frontend" \ 41 | release="$version" \ 42 | summary="Tornjak frontend image" \ 43 | url="" \ 44 | vcs-ref="" \ 45 | vcs-type="" \ 46 | vendor="" \ 47 | version="$version" 48 | # create env. variables with the build details 49 | ENV VERSION=$version 50 | ENV GITHUB_SHA=$github_sha 51 | -------------------------------------------------------------------------------- /frontend/Dockerfile.frontend-container.ubi: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine3.14 AS build 2 | WORKDIR /usr/src/app 3 | COPY frontend . 4 | RUN npm install && \ 5 | npm run build 6 | 7 | ## Runtime stage 8 | FROM registry.access.redhat.com/ubi9/nodejs-18-minimal:latest AS runtime 9 | WORKDIR /opt/app-root/src 10 | COPY --from=build --chown=1001:0 /usr/src/app/build ./build 11 | COPY --from=build --chown=1001:0 /usr/src/app/.env.prod . 12 | 13 | # Install serve package and react-inject-env 14 | RUN npm install -g npm@10.7.0 && \ 15 | npm install --location=global serve && \ 16 | npm install react-inject-env 17 | RUN mkdir build/tmp 18 | 19 | # Update permissions after build 20 | USER 0 21 | RUN chmod -R g+rw /opt/app-root/src 22 | USER 1001 23 | 24 | 25 | # Set dynamic port, defualt 3000 26 | ENV PORT_FE=3000 27 | EXPOSE $PORT_FE 28 | 29 | # moving env.js to fix "access denied" error when running in restricted (read-only) env 30 | ENTRYPOINT npx react-inject-env set -n tmp/env.js && serve -s build -p $PORT_FE 31 | 32 | # add a version link to the image description 33 | ARG version 34 | ARG github_sha 35 | LABEL org.opencontainers.image.description="Tornjak frontend ($version) UBI based Image: https://github.com/spiffe/tornjak/releases/tag/$version" \ 36 | org.opencontainers.image.source="https://github.com/spiffe/tornjak" \ 37 | org.opencontainers.image.documentation="https://github.com/spiffe/tornjak/tree/main/docs" 38 | # replace UBI labels 39 | LABEL architecture="amd64" \ 40 | build-date="" \ 41 | description="Tornjak Frontend" \ 42 | io.k8s.description="Tornjak Frontend" \ 43 | io.k8s.display-name="tornjak-frontend" \ 44 | maintainer="" \ 45 | name="spiffe/tornjak-frontend" \ 46 | release="$version" \ 47 | summary="Tornjak frontend UBI image" \ 48 | url="" \ 49 | vcs-ref="" \ 50 | vcs-type="" \ 51 | vendor="" \ 52 | version="$version" 53 | # create env. variables with the build details 54 | ENV VERSION=$version 55 | ENV GITHUB_SHA=$github_sha 56 | -------------------------------------------------------------------------------- /frontend/axios.d.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig as OriginalAxiosRequestConfig } from "axios"; 2 | 3 | declare module "axios" { 4 | export interface AxiosRequestConfig extends OriginalAxiosRequestConfig { 5 | // custom properties 6 | crossdomain: boolean; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "jsdom", 3 | } -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .app-container { 2 | display: flex; 3 | /*height: 100vh;*/ 4 | /*overflow: hidden;*/ 5 | } 6 | 7 | .sidebar { 8 | position: sticky; 9 | top: 0; 10 | align-self: flex-start; 11 | } 12 | 13 | .main-content { 14 | flex: 1; 15 | flex-direction: column; 16 | overflow-y: auto; 17 | } 18 | 19 | .main{ 20 | padding: 20px; 21 | margin-top: -20px; 22 | } 23 | 24 | .App { 25 | text-align: center; 26 | } 27 | 28 | .App-logo { 29 | height: 40vmin; 30 | pointer-events: none; 31 | } 32 | 33 | @media (prefers-reduced-motion: no-preference) { 34 | .App-logo { 35 | animation: App-logo-spin infinite 20s linear; 36 | } 37 | } 38 | 39 | .App-header { 40 | background-color: #282c34; 41 | min-height: 100vh; 42 | display: flex; 43 | flex-direction: column; 44 | align-items: center; 45 | justify-content: center; 46 | font-size: calc(10px + 2vmin); 47 | color: white; 48 | } 49 | 50 | .App-link { 51 | color: #61dafb; 52 | } 53 | 54 | @keyframes App-logo-spin { 55 | from { 56 | transform: rotate(0deg); 57 | } 58 | to { 59 | transform: rotate(360deg); 60 | } 61 | } 62 | 63 | .endbanneroutput { 64 | margin-left: -30px; 65 | margin-top: 1%; 66 | width: 110%; 67 | height: 30px; 68 | position: fixed; 69 | bottom: 0; 70 | z-index: 2; 71 | } 72 | 73 | .carbon-toast .Toastify__toast { 74 | padding: 0 !important; 75 | /* background: $inverse-02 !important; */ 76 | } 77 | 78 | .carbon-toast .Toastify__toast > svg { 79 | margin-top: 1.375rem; 80 | margin-right: 1rem; 81 | /* fill: $inverse-01; */ 82 | } 83 | 84 | .carbon-toast .Toastify__toast-body { 85 | margin: 0 !important; 86 | padding: 0 !important; 87 | } 88 | 89 | .carbon-toast .Toastify__toast-container { 90 | width: auto !important; 91 | height: auto !important; 92 | } 93 | 94 | .carbon-toast .bx--toast-notification{ 95 | margin: 0; 96 | } -------------------------------------------------------------------------------- /frontend/src/Utils/index.js: -------------------------------------------------------------------------------- 1 | import checkPropTypes from "check-prop-types"; 2 | import {applyMiddleware, createStore} from 'redux'; 3 | import allReducers from "../../src/redux/reducers"; //import all reducers 4 | import { middlewares } from "../../src/redux/store" 5 | 6 | // findByTestId takes in a component and it's testId 7 | // returns compItem - the component 8 | export const findByTestId = (comp, testId) => { 9 | const compItem = comp.find(`[data-test='${testId}']`); 10 | return compItem; 11 | } 12 | 13 | // checkProps takes in a component and expected props 14 | // returns propsErr after checking the component with the expected props 15 | export const checkProps = (comp, expectedProps) => { 16 | const propsErr = checkPropTypes(comp.propTypes, expectedProps, 'props', comp.name); 17 | return propsErr; 18 | } 19 | 20 | // testReduxStore takes in initail state 21 | // returns test redux store for testing purposes 22 | export const testReduxStore = (initialState) => { 23 | const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore); 24 | return createStoreWithMiddleware(allReducers, initialState); 25 | } -------------------------------------------------------------------------------- /frontend/src/auth/KeycloakAuth.js: -------------------------------------------------------------------------------- 1 | import Keycloak from "keycloak-js"; 2 | import {env} from '../env'; 3 | const keycloakConfig = { 4 | "realm": env.REACT_APP_KEYCLOAK_REALM, 5 | "url": env.REACT_APP_AUTH_SERVER_URI, 6 | "ssl-required": "external", 7 | "clientId": env.REACT_APP_OIDC_CLIENT_ID, 8 | "public-client": true, 9 | "verify-token-audience": true, 10 | "use-resource-role-mappings": true, 11 | "confidential-port": 0 12 | }; 13 | const keycloak = new Keycloak(keycloakConfig); 14 | const initKeycloak = (renderApp) => { 15 | keycloak.init({ onLoad: 'login-required' }) 16 | .then((authenticated) => { 17 | if (authenticated) { 18 | console.log("User is authenticated...Redirecting to Tornjak App!"); 19 | renderApp(); 20 | } else { 21 | console.log("User is not authenticated...Redirecting to Keycloak Login!"); 22 | doLogin(); 23 | } 24 | }) 25 | .catch(console.error); 26 | }; 27 | 28 | // get user attributes 29 | const getFirstName = () => keycloak.tokenParsed?.given_name; 30 | 31 | // login 32 | const isLoggedIn = () => !!keycloak.token; 33 | const doLogin = keycloak.login; 34 | 35 | // logout 36 | const doLogout = keycloak.logout; 37 | 38 | // token 39 | const getToken = () => keycloak.token; 40 | const updateToken = (successCallback) => 41 | keycloak.updateToken() 42 | .then(successCallback) 43 | .catch(doLogin); 44 | 45 | const KeycloakService = { 46 | initKeycloak, 47 | getFirstName, 48 | isLoggedIn, 49 | doLogin, 50 | doLogout, 51 | getToken, 52 | updateToken, 53 | }; 54 | 55 | export default KeycloakService; -------------------------------------------------------------------------------- /frontend/src/charts/PieChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PieChart } from "@carbon/charts-react"; 3 | import "@carbon/charts/styles.css"; 4 | import "@carbon/styles/css/styles.css"; 5 | import { connect } from 'react-redux'; 6 | import { RootState } from "redux/reducers"; 7 | import { Alignments, LegendPositions, PieChartOptions } from "@carbon/charts"; 8 | import { PieChartEntry } from "components/types"; 9 | 10 | 11 | type PieChartProps = { 12 | data: PieChartEntry[], 13 | title: string 14 | } 15 | 16 | type PieChartState = { 17 | options: PieChartOptions 18 | } 19 | 20 | class PieChart1 extends React.Component { 21 | constructor(props:PieChartProps) { 22 | super(props); 23 | this.state = { 24 | options: { 25 | title: props.title, 26 | resizable: true, 27 | height: "300px", 28 | legend: { 29 | position: LegendPositions.RIGHT, 30 | truncation: { 31 | "type": "mid_line", 32 | "threshold": 15, 33 | "numCharacter": 12 34 | }, 35 | }, 36 | pie: { 37 | alignment: Alignments.CENTER, 38 | } 39 | } 40 | }; 41 | } 42 | 43 | render() { 44 | const { data, title } = this.props; 45 | return ( 46 |
47 |

{title}

48 | 52 |
53 | ); 54 | } 55 | } 56 | 57 | const mapStateToProps = (state: RootState) => ({ 58 | }) 59 | 60 | export default connect( 61 | mapStateToProps, 62 | {} 63 | )(PieChart1); -------------------------------------------------------------------------------- /frontend/src/components/AccessNotAllowed.tsx: -------------------------------------------------------------------------------- 1 | const AccessNotAllowed = () => ( 2 |

5 | Access is not allowed! ⛔️ 6 |

7 | ) 8 | 9 | export default AccessNotAllowed -------------------------------------------------------------------------------- /frontend/src/components/NavDropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { ChevronDown } from '@carbon/icons-react'; 4 | 5 | interface NavDropdownProps { 6 | icon: React.ReactNode; 7 | title: string; 8 | link: string; 9 | isAdmin: boolean; 10 | withAuth: boolean; 11 | subLinks: Array<{ 12 | label: string; 13 | to: string; 14 | adminOnly?: boolean; 15 | }>; 16 | } 17 | 18 | interface NavDropdownState { 19 | isOpen: boolean; 20 | } 21 | 22 | class NavDropdown extends React.Component { 23 | constructor(props: NavDropdownProps) { 24 | super(props); 25 | this.state = { 26 | isOpen: false, 27 | }; 28 | } 29 | 30 | toggleDropdown = () => { 31 | this.setState((prevState) => ({ 32 | isOpen: !prevState.isOpen, 33 | })); 34 | }; 35 | 36 | render() { 37 | const { icon, title, link, isAdmin, withAuth, subLinks } = this.props; 38 | const { isOpen } = this.state; 39 | 40 | return ( 41 |
42 |
43 | {icon} 44 | 45 | {title} 46 | 47 | 51 |
52 | {isOpen && ( 53 |
54 | {subLinks.map((subLink, index) => { 55 | if (subLink.adminOnly && !(isAdmin || !withAuth)) { 56 | return null; 57 | } 58 | return ( 59 | 60 | {subLink.label} 61 | 62 | ); 63 | })} 64 |
65 | )} 66 |
67 | ); 68 | } 69 | } 70 | 71 | export default NavDropdown; -------------------------------------------------------------------------------- /frontend/src/components/RenderOnAdminRole.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import TornjakHelper from './tornjak-helper'; 3 | import AccessNotAllowed from './AccessNotAllowed' 4 | import { connect } from 'react-redux'; 5 | import { RootState } from 'redux/reducers'; 6 | import {env} from '../env'; 7 | 8 | const Auth_Server_Uri = env.REACT_APP_AUTH_SERVER_URI; 9 | 10 | type RenderOnAdminRoleProp = { 11 | // updated user roles 12 | globalUserRoles: string[], 13 | children: React.ReactNode 14 | } 15 | 16 | type RenderOnAdminRoleState = {} 17 | 18 | class RenderOnAdminRole extends Component { 19 | TornjakHelper: TornjakHelper; 20 | constructor(props: RenderOnAdminRoleProp) { 21 | super(props); 22 | this.TornjakHelper = new TornjakHelper(props); 23 | this.state = {}; 24 | } 25 | 26 | checkPath() { 27 | const pathsWithAdminRestr = [ 28 | "/entry/create", 29 | "/agent/createjointoken", 30 | "/cluster/clustermanagement"]; 31 | const isPath = pathsWithAdminRestr.find(element => { 32 | if(window.location.pathname !== "/") { 33 | return element.includes(window.location.pathname) 34 | } 35 | return undefined; 36 | }); 37 | return isPath; 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | {!Auth_Server_Uri && 44 | this.props.children // if No IAM return children 45 | } 46 | {this.TornjakHelper.checkRolesAdminUser(this.props.globalUserRoles) && 47 | this.props.children // if IAM and admin role return children 48 | } 49 | {!this.TornjakHelper.checkRolesAdminUser(this.props.globalUserRoles) && this.checkPath() && Auth_Server_Uri && 50 | // if IAM and no admin role return access not allowed 51 | } 52 |
53 | ); 54 | } 55 | } 56 | 57 | const mapStateToProps = (state: RootState) => ({ 58 | globalUserRoles: state.auth.globalUserRoles 59 | }) 60 | 61 | export default connect(mapStateToProps, {})(RenderOnAdminRole) -------------------------------------------------------------------------------- /frontend/src/components/__tests__/__snapshots__/cluster-list.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Cluster List Component Should Render Properly Should Render 1`] = ` 4 |
7 |

8 | Clusters List 9 |

10 |
14 |
15 |       Test String
16 |     
17 |
18 |
19 |
20 |
23 | 27 |
28 |
29 | `; 30 | -------------------------------------------------------------------------------- /frontend/src/components/__tests__/cluster-create.test.jsx: -------------------------------------------------------------------------------- 1 | import { ClusterCreate } from "../../components/cluster-create" 2 | import { shallow, configure } from "enzyme" 3 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17" 4 | 5 | configure({ adapter: new Adapter() }) 6 | 7 | const props = { 8 | serverInfoUpdateFunc: jest.fn(), 9 | agentsListUpdateFunc: jest.fn(), 10 | tornjakMessageFunc: jest.fn(), 11 | tornjakServerInfoUpdateFunc: jest.fn(), 12 | clusterTypeInfoFunc: jest.fn(), 13 | agentsList: [{}], 14 | clusterTypeList: [], 15 | globalServerSelected: "Test String", 16 | globalErrorMessage: "Test String", 17 | globalTornjakServerInfo: {} 18 | } 19 | 20 | describe("Cluster Create Component", () => { 21 | const wrapper = shallow() 22 | 23 | it("Should Render", () => { 24 | expect(wrapper).toMatchSnapshot() 25 | }) 26 | 27 | it("Should Create Cluster", () => { 28 | const instance = wrapper.instance() 29 | 30 | instance.onChangeClusterName({target: {value: "Name"}}) 31 | instance.onChangeManualClusterType({target: {value: "Type"}}) 32 | instance.onChangeClusterDomainName({target: {value: "Domain"}}) 33 | instance.onChangeClusterManagedBy({target: {value: "Managed By"}}) 34 | 35 | wrapper.find("form").simulate("submit") 36 | 37 | expect(wrapper.state("clusterName")).toBe("Name") 38 | expect(wrapper.state("clusterType")).toBe("Type") 39 | expect(wrapper.state("clusterDomainName")).toBe("Domain") 40 | expect(wrapper.state("clusterManagedBy")).toBe("Managed By") 41 | }) 42 | }) -------------------------------------------------------------------------------- /frontend/src/components/apiConfig.ts: -------------------------------------------------------------------------------- 1 | import {env} from '../env'; 2 | 3 | const API_VERSION = env.REACT_APP_API_VERSION; 4 | const API_BASE_URL = `/api/${API_VERSION}`; 5 | const apiEndpoints = { 6 | spireServerInfoApi: `${API_BASE_URL}/spire/serverinfo`, 7 | spireHealthCheckApi: `${API_BASE_URL}/spire/healthcheck`, 8 | spireAgentsApi: `${API_BASE_URL}/spire/agents`, 9 | spireAgentsBanApi: `${API_BASE_URL}/spire/agents/ban`, 10 | spireJoinTokenApi: `${API_BASE_URL}/spire/agents/jointoken`, 11 | spireEntriesApi: `${API_BASE_URL}/spire/entries`, 12 | spireFederationsApi: `${API_BASE_URL}/spire/federations`, 13 | tornjakServerInfoApi: `${API_BASE_URL}/tornjak/serverinfo`, 14 | tornjakSelectorsApi: `${API_BASE_URL}/tornjak/selectors`, 15 | tornjakAgentsApi: `${API_BASE_URL}/tornjak/agents`, 16 | tornjakClustersApi: `${API_BASE_URL}/tornjak/clusters`, 17 | }; 18 | 19 | export default apiEndpoints; 20 | -------------------------------------------------------------------------------- /frontend/src/components/dashboard/agents-pie-chart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PieChart1 from "charts/PieChart"; 4 | import SpiffeHelper from '../spiffe-helper' 5 | import { AgentsList, EntriesList } from 'components/types'; 6 | import { RootState } from 'redux/reducers'; 7 | import { AgentsReducerState, EntriesReducerState } from 'redux/actions/types'; 8 | 9 | type AgentsPieChartProp = { 10 | globalAgents: AgentsReducerState, 11 | globalEntries: EntriesReducerState 12 | } 13 | class AgentsPieChart extends React.Component { 14 | SpiffeHelper: SpiffeHelper; 15 | constructor(props: AgentsPieChartProp) { 16 | super(props); 17 | this.SpiffeHelper = new SpiffeHelper(props); 18 | } 19 | 20 | getChildEntries(agent: AgentsList, agentEntriesDict: { 21 | [key: string]: EntriesList[]; 22 | } | undefined) { 23 | var spiffeid = this.SpiffeHelper.getAgentSpiffeid(agent); 24 | var validIds = new Set([spiffeid]); 25 | 26 | // Also check for parent IDs associated with the agent 27 | let agentEntries = agentEntriesDict ? agentEntriesDict[spiffeid] : undefined; 28 | if (agentEntries !== undefined) { 29 | for (let j = 0; j < agentEntries.length; j++) { 30 | validIds.add(this.SpiffeHelper.getEntrySpiffeid(agentEntries[j])); 31 | } 32 | } 33 | 34 | var check_id; 35 | if (typeof this.props.globalEntries.globalEntriesList !== 'undefined') { 36 | check_id = this.props.globalEntries.globalEntriesList.filter((thisentry: EntriesList) => { 37 | return validIds.has(this.SpiffeHelper.getEntryParentid(thisentry)); 38 | }); 39 | } 40 | if (typeof check_id === 'undefined') { 41 | return { 42 | "group": spiffeid, 43 | "value": 0, 44 | } 45 | } else { 46 | return { 47 | "group": spiffeid, 48 | "value": check_id.length, 49 | } 50 | } 51 | } 52 | 53 | agentList() { 54 | if ((typeof this.props.globalEntries.globalEntriesList === 'undefined') || 55 | (typeof this.props.globalAgents.globalAgentsList === 'undefined')) { 56 | return []; 57 | } 58 | 59 | let agentEntriesDict = this.SpiffeHelper.getAgentsEntries(this.props.globalAgents.globalAgentsList, this.props.globalEntries.globalEntriesList) 60 | var valueMapping = this.props.globalAgents.globalAgentsList.map((currentAgent: AgentsList) => { 61 | return this.getChildEntries(currentAgent, agentEntriesDict); 62 | }) 63 | return valueMapping 64 | } 65 | 66 | render() { 67 | var groups = this.agentList() 68 | return ( 69 | 70 | {groups.length === 0 && 71 |

No Data To Display

72 | } 73 | {groups.length !== 0 && 74 | 78 | } 79 |
80 | ); 81 | } 82 | } 83 | 84 | const mapStateToProps = (state: RootState) => ({ 85 | globalAgents: state.agents, 86 | globalEntries: state.entries, 87 | }) 88 | 89 | export default connect(mapStateToProps, {})(AgentsPieChart) -------------------------------------------------------------------------------- /frontend/src/components/dashboard/clusters-dashboard-table.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { withStyles } from 'tss-react/mui'; 4 | import TableDashboard from './table/dashboard-table'; 5 | import SpiffeHelper from '../spiffe-helper'; 6 | import TornjakHelper from 'components/tornjak-helper'; 7 | import { GridColDef } from '@mui/x-data-grid'; 8 | import { AgentsReducerState, EntriesReducerState } from 'redux/actions/types'; 9 | import { RootState } from 'redux/reducers'; 10 | import { ClustersList } from 'components/types'; 11 | 12 | const columns: GridColDef[] = [ 13 | { field: "name", headerName: "Name", width: 200 }, 14 | { field: "created", headerName: "Created", width: 300 }, 15 | { field: "numNodes", headerName: "Number Of Nodes", width: 300 }, 16 | { field: "numEntries", headerName: "Number of Entries", width: 200 } 17 | ]; 18 | 19 | interface ClusterDashboardTableProp { 20 | filterByCluster?:string, 21 | filterByAgentId?:string, 22 | globalClickedDashboardTable: string, 23 | numRows: number, 24 | //From Redux 25 | globalClustersList: ClustersList[], 26 | globalAgents: AgentsReducerState, 27 | globalEntries: EntriesReducerState 28 | } 29 | 30 | class ClusterDashboardTable extends React.Component { 31 | TornjakHelper: TornjakHelper; 32 | SpiffeHelper: SpiffeHelper; 33 | constructor(props:ClusterDashboardTableProp) { 34 | super(props); 35 | this.SpiffeHelper = new SpiffeHelper({}); 36 | this.TornjakHelper = new TornjakHelper({}); 37 | } 38 | 39 | clusterList() { 40 | var filterByValue = []; 41 | const { filterByCluster } = this.props; 42 | let clustersList = []; 43 | if (typeof this.props.globalClustersList === 'undefined') { 44 | return []; 45 | } 46 | clustersList = this.props.globalClustersList.map(a => this.TornjakHelper.getClusterMetadata(a, this.props.globalEntries.globalEntriesList, this.props.globalAgents.globalAgentsList)); 47 | //For details page filtering data 48 | if (filterByCluster === undefined) { 49 | return clustersList; 50 | } 51 | for (let i = 0; i < clustersList.length; i++) { 52 | if ((filterByCluster !== undefined && clustersList[i].name === filterByCluster)) { 53 | filterByValue.push(clustersList[i]); 54 | } 55 | } 56 | return filterByValue; 57 | } 58 | 59 | render() { 60 | const { numRows } = this.props; 61 | var data = this.clusterList(); 62 | return ( 63 |
64 | 69 |
70 | ); 71 | } 72 | } 73 | 74 | const mapStateToProps = (state:RootState) => ({ 75 | globalClustersList: state.clusters.globalClustersList, 76 | globalAgents: state.agents, 77 | globalEntries: state.entries, 78 | }) 79 | 80 | const ClusterDashboardTableStyled = withStyles( 81 | ClusterDashboardTable, 82 | (theme: { spacing: (arg0: number) => any; }) => ({ 83 | root: { 84 | seeMore: { 85 | marginTop: theme.spacing(3), 86 | }, 87 | } 88 | }) 89 | ); 90 | 91 | export default connect(mapStateToProps, {})(ClusterDashboardTableStyled); -------------------------------------------------------------------------------- /frontend/src/components/dashboard/clusters-pie-chart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PieChart1 from "charts/PieChart"; 4 | import { RootState } from 'redux/reducers'; 5 | 6 | type ClustersPieChartProps = { 7 | globalClustersList: Array<{ [key: string]: any }>; 8 | } 9 | 10 | class ClustersPieChart extends React.Component { 11 | cluster(entry: { [key: string]: any }) { 12 | return { 13 | "group": entry.name, 14 | "value": entry.agentsList.length 15 | } 16 | } 17 | 18 | clusterList() { 19 | if (typeof this.props.globalClustersList !== 'undefined') { 20 | return this.props.globalClustersList.map(currentCluster => { 21 | return this.cluster(currentCluster); 22 | }) 23 | } else { 24 | return [] 25 | } 26 | } 27 | 28 | render() { 29 | var sections = this.clusterList() 30 | return ( 31 | 32 | {sections.length === 0 && 33 |

No Data To Display

34 | } 35 | {sections.length !== 0 && 36 | 40 | } 41 |
42 | ); 43 | } 44 | } 45 | 46 | const mapStateToProps = (state: RootState) => ({ 47 | globalClustersList: state.clusters.globalClustersList, 48 | }) 49 | 50 | export default connect(mapStateToProps, {})(ClustersPieChart) -------------------------------------------------------------------------------- /frontend/src/components/dashboard/dashboard-drawer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { withStyles } from 'tss-react/mui'; 4 | import { CssBaseline } from '@mui/material'; 5 | import { Tabs, TabList, Tab} from '@carbon/react'; 6 | import { 7 | clickedDashboardTableFunc, 8 | } from 'redux/actions'; 9 | 10 | const styles = theme => ({ 11 | root: { 12 | marginTop: 0, 13 | display: 'flex', 14 | flexDirection: 'column', 15 | }, 16 | title: { 17 | flexGrow: 1, 18 | color: 'black', 19 | marginTop: 20, 20 | }, 21 | content: { 22 | flexGrow: 1, 23 | }, 24 | container: { 25 | paddingTop: theme.spacing(4), 26 | paddingBottom: theme.spacing(4), 27 | marginLeft: 0, 28 | }, 29 | paper: { 30 | padding: theme.spacing(2), 31 | display: 'flex', 32 | overflow: 'auto', 33 | flexDirection: 'column', 34 | }, 35 | fixedHeight: { 36 | height: 370, 37 | }, 38 | h3: { 39 | color: 'black', 40 | marginTop: 20, 41 | marginLeft: 20, 42 | marginBottom: 10, 43 | }, 44 | 45 | tabList: { 46 | marginLeft: '20px', 47 | marginBottom: '-80px', 48 | }, 49 | }); 50 | 51 | class DashboardDrawer extends React.Component { 52 | constructor(props) { 53 | super(props); 54 | let selectedTab = 0; 55 | const path = window.location.pathname; 56 | if (path.includes("clusters")) { 57 | selectedTab = 1; 58 | } else if (path.includes("agents")) { 59 | selectedTab = 2; 60 | } else if (path.includes("entries")) { 61 | selectedTab = 3; 62 | } 63 | this.state = { 64 | selectedTab: selectedTab, 65 | }; 66 | } 67 | 68 | assignDashboardPath(entity, tabIndex) { 69 | this.props.clickedDashboardTableFunc(entity); 70 | this.setState({ selectedTab: tabIndex }); 71 | const path = "/tornjak/dashboard"; 72 | if (window.location.href !== window.location.origin + path) 73 | window.location.href = path; 74 | } 75 | 76 | render() { 77 | const classes = withStyles.getClasses(this.props); 78 | return ( 79 |
80 | 81 |
82 |

Tornjak Dashboard

83 |
84 | 85 | 86 | this.assignDashboardPath("dashboard", 0)}>Dashboard 87 | this.assignDashboardPath("clusters", 1)}>Clusters 88 | this.assignDashboardPath("agents", 2)}>Agents 89 | this.assignDashboardPath("entries", 3)}>Entries 90 | 91 | 92 | 93 |
94 | ); 95 | } 96 | } 97 | 98 | const mapStateToProps = (state) => ({ 99 | globalClickedDashboardTable: state.tornjak.globalClickedDashboardTable, 100 | }); 101 | 102 | const DashboardDrawerStyled = withStyles(DashboardDrawer, styles); 103 | export default connect(mapStateToProps, { clickedDashboardTableFunc })(DashboardDrawerStyled); 104 | -------------------------------------------------------------------------------- /frontend/src/components/dashboard/table/dashboard-table.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from 'react-redux'; 3 | import { DataGrid, GridColDef, GridToolbar } from "@mui/x-data-grid"; 4 | import Title from '../title'; 5 | import { Button } from '@mui/material'; 6 | import { clickedDashboardTableFunc } from 'redux/actions'; 7 | import TornjakHelper from 'components/tornjak-helper'; 8 | import { RootState } from "redux/reducers"; 9 | 10 | type TableDashboardProp = { 11 | // dispatches a payload for the clicked table in a dashboard as a string and has a return type of void 12 | clickedDashboardTableFunc: (globalClickedDashboardTable: string) => void, 13 | // the clicked dashboard table 14 | globalClickedDashboardTable: string, 15 | numRows: number, 16 | title: string, 17 | columns: GridColDef[], 18 | data: {[key:string]:any}[] 19 | } 20 | 21 | type TableDashboardState = { 22 | selectedRows: string 23 | } 24 | 25 | class TableDashboard extends React.Component { 26 | TornjakHelper: TornjakHelper; 27 | constructor(props: TableDashboardProp) { 28 | super(props); 29 | this.state = { 30 | selectedRows: "", 31 | }; 32 | this.TornjakHelper = new TornjakHelper({}); 33 | } 34 | 35 | render() { 36 | const { data, columns, title } = this.props; 37 | return ( 38 | 39 | 40 | <Button 41 | color="inherit" 42 | size="large" 43 | onClick={() => { this.props.clickedDashboardTableFunc(title.toLowerCase()); }} 44 | > 45 | {title} 46 | </Button> 47 | 48 | 64 |
65 | { 70 | if (newSelection[0]) this.setState({ selectedRows: newSelection[0].toString() }) 71 | }} 72 | slots={{ 73 | toolbar: GridToolbar, 74 | }} 75 | /> 76 |
77 |
78 | ); 79 | } 80 | } 81 | 82 | const mapStateToProps = (state:RootState) => ({ 83 | globalClickedDashboardTable: state.tornjak.globalClickedDashboardTable, 84 | }) 85 | 86 | export default connect( 87 | mapStateToProps, 88 | { clickedDashboardTableFunc } 89 | )(TableDashboard); 90 | -------------------------------------------------------------------------------- /frontend/src/components/dashboard/title.tsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import Typography from '@mui/material/Typography'; 3 | 4 | type TitleProps = { 5 | children: React.ReactNode 6 | } 7 | 8 | export default function Title(props: TitleProps) { 9 | return ( 10 | 11 | {props.children} 12 | 13 | ); 14 | } 15 | 16 | Title.propTypes = { 17 | children: PropTypes.node, 18 | }; -------------------------------------------------------------------------------- /frontend/src/components/error-api.tsx: -------------------------------------------------------------------------------- 1 | import { toast, ToastContent, ToastOptions } from "react-toastify" 2 | import { ToastNotification, ToastNotificationProps } from "carbon-components-react" 3 | 4 | interface NotificationProps extends Omit { 5 | title?: string 6 | } 7 | 8 | const defualtProps: ToastNotificationProps = {title: "Notification", kind: "error"} 9 | const defaultOptions: ToastContent = {autoClose: false, closeButton: false, role: "alert"} 10 | 11 | export const showToast = (props?: NotificationProps, options?: ToastOptions): void => { 12 | const newProps = {...defualtProps, ...props} 13 | toast(, {...defaultOptions, ...options}) 14 | } 15 | 16 | type Response = {response: {data: string, status: number}} 17 | 18 | const defaultResponseProps = (res: Response): NotificationProps => { 19 | if (res.response === undefined) { 20 | return {caption: "Could not connect to backend", title: "Network Error"} 21 | } 22 | return {caption: res.response.data, title: "Error " + String(res.response.status)} 23 | } 24 | 25 | export const showResponseToast = (res: Response, props?: NotificationProps, options?: ToastOptions): void => { 26 | showToast({...defaultResponseProps(res), ...props}, options) 27 | } -------------------------------------------------------------------------------- /frontend/src/components/helpers.ts: -------------------------------------------------------------------------------- 1 | import {env} from '../env'; 2 | var urljoin = require('url-join'); 3 | 4 | // API_SERVER_URL 5 | const ApiServerUri = env["REACT_APP_API_SERVER_URI"]; 6 | 7 | export default function GetApiServerUri (uri: string): string { 8 | return urljoin(ApiServerUri ? ApiServerUri : "/", uri) 9 | } 10 | 11 | // const IS_DUBUG = process.env["REACT_APP_DEBUG_TORNJAK"] || window.DEBUG_TORNJAK; 12 | // console.log(process.env["REACT_APP_DEBUG_TORNJAK"]); 13 | // console.log(window.DEBUG_TORNJAK); 14 | 15 | export const logDebug = function (...args: any[]) { 16 | if (process.env["REACT_APP_DEBUG_TORNJAK"]) { // real time variable 17 | console.log(...args); 18 | } 19 | }; 20 | 21 | export const logError = function (...args: any[]) { 22 | if (process.env["REACT_APP_DEBUG_TORNJAK"]) { // real time variable 23 | console.error(...args); 24 | } 25 | }; 26 | 27 | export const logWarn = function (...args: any[]) { 28 | if (process.env["REACT_APP_DEBUG_TORNJAK"]) { // real time variable 29 | console.warn(...args); 30 | } 31 | }; 32 | 33 | 34 | // IS_MANAGER 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/is_manager.ts: -------------------------------------------------------------------------------- 1 | import {env} from '../env'; 2 | 3 | // Is Manager 4 | console.log("IS_MANAGER:" + env.REACT_APP_TORNJAK_MANAGER) 5 | const IsManager = env.REACT_APP_TORNJAK_MANAGER !== undefined && env.REACT_APP_TORNJAK_MANAGER.toUpperCase() !== "FALSE" 6 | 7 | export default IsManager 8 | -------------------------------------------------------------------------------- /frontend/src/components/navbar-header-toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { HeaderGlobalAction } from "carbon-components-react"; 3 | import { UserAvatar, Notification, Search } from "@carbon/icons-react"; 4 | import KeycloakService from "auth/KeycloakAuth"; 5 | import {env} from '../env'; 6 | 7 | const Auth_Server_Uri = env.REACT_APP_AUTH_SERVER_URI; 8 | 9 | type HeaderToolBarProp = {} 10 | 11 | type HeaderToolBarState = {} 12 | 13 | class HeaderToolBar extends Component { 14 | constructor(props: HeaderToolBarProp) { 15 | super(props); 16 | this.state = {}; 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 | {Auth_Server_Uri && 23 |
24 | 26 | 27 | 28 |
29 | {KeycloakService.isLoggedIn() && ( 30 | // eslint-disable-next-line 31 | KeycloakService.doLogout()}> 35 | Logout {KeycloakService.getFirstName()} 36 | 37 | )} 38 |
39 |
40 | } 41 | 42 | { alert("This is a place holder, functionality to be implemented on future work!") }}> 45 | 46 | 47 | { alert("This is a place holder, functionality to be implemented on future work!") }}> 50 | 51 | 52 | 53 |
54 | ); 55 | } 56 | } 57 | export default HeaderToolBar; -------------------------------------------------------------------------------- /frontend/src/data/data.ts: -------------------------------------------------------------------------------- 1 | const clusterType = 2 | [ 3 | "Kubernetes", 4 | "VMs", 5 | ]; 6 | 7 | const selectors = 8 | { 9 | "aws_iid": [ 10 | { 11 | "label": "aws_iid:tag:name" 12 | }, 13 | { 14 | "label": "aws_iid:sg:id" 15 | }, 16 | { 17 | "label": "aws_iid:sg:name" 18 | }, 19 | { 20 | "label": "aws_iid:iamrole:arn:aws:iam" 21 | } 22 | ], 23 | "gcp_iit": [ 24 | { 25 | "label": "gcp_iit:project-id" 26 | }, 27 | { 28 | "label": "gcp_iit:zone" 29 | }, 30 | { 31 | "label": "gcp_iit:instance-name" 32 | }, 33 | { 34 | "label": "gcp_iit:tag" 35 | }, 36 | { 37 | "label": "gcp_iit:sa" 38 | }, 39 | { 40 | "label": "gcp_iit:label" 41 | }, 42 | { 43 | "label": "gcp_iit:metadata" 44 | } 45 | ], 46 | "k8s_sat": [ 47 | { 48 | "label": "k8s_sat:cluster" 49 | }, 50 | { 51 | "label": "k8s_sat:agent_ns" 52 | }, 53 | { 54 | "label": "k8s_sat:agent_sa" 55 | } 56 | ], 57 | "k8s_psat": [ 58 | { 59 | "label": "k8s_psat:cluster" 60 | }, 61 | { 62 | "label": "k8s_psat:agent_ns" 63 | }, 64 | { 65 | "label": "k8s_psat:agent_sa" 66 | }, 67 | { 68 | "label": "k8s_psat:agent_pod_name" 69 | }, 70 | { 71 | "label": "k8s_psat:agent_pod_uid" 72 | }, 73 | { 74 | "label": "k8s_psat:agent_pod_label" 75 | }, 76 | { 77 | "label": "k8s_psat:agent_node_ip" 78 | }, 79 | { 80 | "label": "k8s_psat:agent_node_name" 81 | }, 82 | { 83 | "label": "k8s_psat:agent_node_uid" 84 | }, 85 | { 86 | "label": "k8s_psat:agent_node_label" 87 | } 88 | ], 89 | }; 90 | 91 | const workloadSelectors = 92 | { 93 | "Docker": [ 94 | { 95 | "label": "docker:label" 96 | }, 97 | { 98 | "label": "docker:env" 99 | }, 100 | { 101 | "label": "docker:image_id" 102 | }, 103 | ], 104 | "Kubernetes": [ 105 | { 106 | "label": "k8s:ns" 107 | }, 108 | { 109 | "label": "k8s:sa" 110 | }, 111 | { 112 | "label": "k8s:container-image" 113 | }, 114 | { 115 | "label": "k8s:container-name" 116 | }, 117 | { 118 | "label": "k8s:node-name" 119 | }, 120 | { 121 | "label": "k8s:pod-label" 122 | }, 123 | { 124 | "label": "k8s:pod-owner" 125 | }, 126 | { 127 | "label": "k8s:pod-owner-uid" 128 | }, 129 | { 130 | "label": "k8s:pod-uid" 131 | }, 132 | { 133 | "label": "k8s:pod-name" 134 | }, 135 | { 136 | "label": "k8s:pod-image" 137 | }, 138 | { 139 | "label": "k8s:pod-image-count" 140 | }, 141 | { 142 | "label": "k8s:pod-init-image" 143 | }, 144 | { 145 | "label": "k8s:pod-init-image-count" 146 | }, 147 | ], 148 | "Unix": [ 149 | { 150 | "label": "unix:uid" 151 | }, 152 | { 153 | "label": "unix:user" 154 | }, 155 | { 156 | "label": "unix:gid" 157 | }, 158 | { 159 | "label": "unix:group" 160 | }, 161 | { 162 | "label": "unix:supplementary_gid" 163 | }, 164 | { 165 | "label": "unix:supplementary_group" 166 | }, 167 | { 168 | "label": "unix:path" 169 | }, 170 | { 171 | "label": "unix:sha256" 172 | }, 173 | ], 174 | }; 175 | export { clusterType, selectors, workloadSelectors }; 176 | -------------------------------------------------------------------------------- /frontend/src/env.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | env: any 4 | } 5 | } 6 | type EnvType = { 7 | REACT_APP_SPIRE_HEALTH_CHECK_ENABLE: string, 8 | REACT_APP_AUTH_SERVER_URI: string, 9 | REACT_APP_API_SERVER_URI: string, 10 | REACT_APP_TORNJAK_MANAGER: string, 11 | REACT_APP_KEYCLOAK_REALM: string, 12 | REACT_APP_OIDC_CLIENT_ID: string, 13 | REACT_APP_API_VERSION: string, 14 | } 15 | export const env: EnvType = { ...process.env, ...window.env } -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import KeycloakService from "./auth/KeycloakAuth"; 7 | import {env} from './env'; 8 | 9 | const renderApp = () => ReactDOM.render(, document.getElementById('root')); 10 | 11 | if (env.REACT_APP_AUTH_SERVER_URI) { // with Auth for testing purposes 12 | KeycloakService.initKeycloak(renderApp); 13 | } else { 14 | renderApp(); // without Auth 15 | } 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/agentsReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AgentsListAction, 3 | AgentsReducerState, 4 | AgentWorkloadSelectorInfoAction, 5 | GLOBAL_AGENTS_LIST, 6 | GLOBAL_AGENTS_WORKLOADATTESTOR_INFO, 7 | } from '../actions/types'; 8 | 9 | const initialState: AgentsReducerState = { 10 | globalAgentsList: [], 11 | globalAgentsWorkLoadAttestorInfo: [], 12 | }; 13 | 14 | export default function agentsReducer(state: AgentsReducerState = initialState, action: AgentsListAction | AgentWorkloadSelectorInfoAction) { 15 | switch (action.type) { 16 | case GLOBAL_AGENTS_LIST: 17 | return { 18 | ...state, 19 | globalAgentsList: action.payload 20 | }; 21 | case GLOBAL_AGENTS_WORKLOADATTESTOR_INFO: 22 | return { 23 | ...state, 24 | globalAgentsWorkLoadAttestorInfo: action.payload 25 | }; 26 | default: 27 | return state; 28 | } 29 | } -------------------------------------------------------------------------------- /frontend/src/redux/reducers/authReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthReducerState, 3 | IsAuthenticatedAction, 4 | AccessTokenAction, 5 | UserRolesAction, 6 | GLOBAL_IS_AUTHENTICATED, 7 | GLOBAL_ACCESS_TOKEN, 8 | GLOBAL_USER_ROLES, 9 | } from '../actions/types'; 10 | 11 | const initialState: AuthReducerState = { 12 | globalIsAuthenticated: false, 13 | globalAccessToken: "", 14 | globalUserRoles: [], 15 | }; 16 | 17 | export default function authReducer(state: AuthReducerState = initialState, action: IsAuthenticatedAction | AccessTokenAction | UserRolesAction) { 18 | switch (action.type) { 19 | case GLOBAL_IS_AUTHENTICATED: 20 | return { 21 | ...state, 22 | globalIsAuthenticated: action.payload 23 | }; 24 | case GLOBAL_ACCESS_TOKEN: 25 | return { 26 | ...state, 27 | globalAccessToken: action.payload 28 | }; 29 | case GLOBAL_USER_ROLES: 30 | return { 31 | ...state, 32 | globalUserRoles: action.payload 33 | }; 34 | default: 35 | return state; 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/src/redux/reducers/clustersReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClustersListUpdateAction, 3 | ClustersReducerState, 4 | ClusterTypeInfoAction, 5 | GLOBAL_CLUSTERS_LIST, 6 | GLOBAL_CLUSTER_TYPE_INFO, 7 | } from '../actions/types'; 8 | 9 | const initialState: ClustersReducerState = { 10 | globalClustersList: [], 11 | globalClusterTypeInfo: [], 12 | }; 13 | 14 | export default function clustersReducer(state: ClustersReducerState = initialState, action: ClustersListUpdateAction | ClusterTypeInfoAction) { 15 | switch (action.type) { 16 | case GLOBAL_CLUSTERS_LIST: 17 | return { 18 | ...state, 19 | globalClustersList: action.payload 20 | }; 21 | case GLOBAL_CLUSTER_TYPE_INFO: 22 | return { 23 | ...state, 24 | globalClusterTypeInfo: action.payload 25 | }; 26 | default: 27 | return state; 28 | } 29 | } -------------------------------------------------------------------------------- /frontend/src/redux/reducers/entriesReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EntriesListAction, 3 | NewEntriesAction, 4 | EntryExpiryAction, 5 | EntriesReducerState, 6 | GLOBAL_ENTRIES_LIST, 7 | GLOBAL_NEW_ENTRIES, 8 | GLOBAL_ENTRY_EXPIRY 9 | } from '../actions/types'; 10 | 11 | const initialState: EntriesReducerState = { 12 | globalEntriesList: [], 13 | globalNewEntries: [], 14 | globalEntryExpiryTime: 0, 15 | }; 16 | 17 | export default function entriesReducer(state: EntriesReducerState = initialState, action: EntriesListAction | NewEntriesAction | EntryExpiryAction) { 18 | switch (action.type) { 19 | case GLOBAL_ENTRIES_LIST: 20 | return { 21 | ...state, 22 | globalEntriesList: action.payload 23 | }; 24 | case GLOBAL_NEW_ENTRIES: 25 | return { 26 | ...state, 27 | globalNewEntries: action.payload 28 | }; 29 | case GLOBAL_ENTRY_EXPIRY: 30 | return { 31 | ...state, 32 | globalEntryExpiryTime: action.payload 33 | }; 34 | default: 35 | return state; 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/src/redux/reducers/federationsReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FederationsListAction, 3 | FederationsReducerState, 4 | GLOBAL_FEDERATIONS_LIST, 5 | } from '../actions/types'; 6 | 7 | const initialState: FederationsReducerState = { 8 | globalFederationsList: [], 9 | }; 10 | 11 | export default function federationsReducer(state: FederationsReducerState = initialState, action: FederationsListAction) { 12 | switch (action.type) { 13 | case GLOBAL_FEDERATIONS_LIST: 14 | return { 15 | ...state, 16 | globalFederationsList: action.payload 17 | }; 18 | default: 19 | return state; 20 | } 21 | } -------------------------------------------------------------------------------- /frontend/src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import serversReducer from './serversReducer'; 2 | import clustersReducer from './clustersReducer'; 3 | import agentsReducer from './agentsReducer'; 4 | import entriesReducer from './entriesReducer'; 5 | import tornjakReducer from './tornjakReducer'; 6 | import {combineReducers} from 'redux'; 7 | import authReducer from './authReducer'; 8 | import federationsReducer from "./federationsReducer"; 9 | 10 | const allReducers = combineReducers({ 11 | servers : serversReducer, 12 | clusters : clustersReducer, 13 | federations: federationsReducer, 14 | agents : agentsReducer, 15 | entries : entriesReducer, 16 | tornjak: tornjakReducer, 17 | auth: authReducer, 18 | }); 19 | 20 | export type RootState = ReturnType; 21 | 22 | export default allReducers; -------------------------------------------------------------------------------- /frontend/src/redux/reducers/tornjakReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GLOBAL_MESSAGE, 3 | GLOBAL_CLICKED_DASHBOARD_TABLE, 4 | TornjakReducerState, 5 | TornjakAction, 6 | } from '../actions/types'; 7 | 8 | const initialState: TornjakReducerState = { 9 | globalErrorMessage: "", 10 | globalClickedDashboardTable: "", 11 | }; 12 | 13 | export default function tornjakReducer(state: TornjakReducerState = initialState, action: TornjakAction) { 14 | switch (action.type) { 15 | case GLOBAL_MESSAGE: 16 | return { 17 | ...state, 18 | globalErrorMessage: action.payload 19 | }; 20 | case GLOBAL_CLICKED_DASHBOARD_TABLE: 21 | return { 22 | ...state, 23 | globalClickedDashboardTable: action.payload 24 | }; 25 | default: 26 | return state; 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/src/redux/store.ts: -------------------------------------------------------------------------------- 1 | //---redux---// 2 | import {applyMiddleware, createStore} from 'redux'; 3 | import allReducers from './reducers'; //import all reducers 4 | import thunk from "redux-thunk"; 5 | import { composeWithDevTools } from '@redux-devtools/extension'; //for Redox dev 6 | import { RootState } from './reducers'; 7 | 8 | function saveToLocalStorage(state: RootState) { 9 | try { 10 | const serializedState = JSON.stringify(state) 11 | sessionStorage.setItem('state', serializedState) 12 | } catch(e) { 13 | console.log(e) 14 | } 15 | } 16 | 17 | function loadFromLocalStorage() { 18 | try { 19 | const serializedState = sessionStorage.getItem('state') 20 | if (serializedState === null) return undefined 21 | return JSON.parse(serializedState) 22 | } catch(e) { 23 | console.log(e) 24 | return undefined 25 | } 26 | } 27 | 28 | export const middlewares = [thunk]; 29 | const persistedState = loadFromLocalStorage() 30 | // STORE -> Globalized state - where to store all data 31 | const store = createStore( 32 | allReducers, 33 | persistedState, 34 | //applyMiddleware(thunk) 35 | composeWithDevTools(applyMiddleware(thunk)) //for redux devtools extension for dev 36 | ); //store all combined reducers in one 37 | //-------------------------------------------- 38 | 39 | store.subscribe(() => saveToLocalStorage(store.getState())) 40 | export default store; -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/res/tornjak_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/frontend/src/res/tornjak_face.png -------------------------------------------------------------------------------- /frontend/src/res/tornjak_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/frontend/src/res/tornjak_logo.png -------------------------------------------------------------------------------- /frontend/src/tables/table-head.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DataTable, DataTableCustomHeaderData, DataTableCustomHeaderProps, DataTableCustomSelectionData, DataTableCustomSelectionProps, DataTableHeader, DataTableRow } from "carbon-components-react"; 3 | import { ReactAttr, ShapeOf } from "carbon-components-react/typings/shared"; 4 | const { 5 | TableHead, 6 | TableRow, 7 | TableSelectAll, 8 | TableHeader, 9 | } = DataTable; 10 | 11 | // Head takes in 12 | // getSelectionProps: getSelectionProps function for selecting all rows from DataTable 13 | // headers: headerData of table 14 | // getHeaderProps: getHeaderProps function from DataTable 15 | // returns header of the table for the specified entity 16 | type HeadProp = { 17 | headers: DataTableHeader[], 18 | getSelectionProps: (data?: 19 | ShapeOf>, E> | undefined) => 20 | ShapeOf>, E> | 21 | ShapeOf, E>, 22 | getHeaderProps: >(data: ShapeOf>, E>) => 24 | ShapeOf>, E>, 25 | } 26 | 27 | type HeadState = {} 28 | class Head extends React.Component { 29 | constructor(props: HeadProp) { 30 | super(props); 31 | this.state = {}; 32 | } 33 | 34 | render() { 35 | return ( 36 | 37 | 38 | 39 | {this.props.headers.map((header: DataTableHeader) => ( 40 | 41 | {header.header} 42 | 43 | ))} 44 | 45 | 46 | ) 47 | } 48 | }; 49 | 50 | export default (Head) -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "target": "es2017", 5 | "jsx": "react-jsx", 6 | "module": "esnext", 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "skipLibCheck": true, 13 | "lib": [ 14 | "dom", 15 | "dom.iterable", 16 | "esnext" 17 | ], 18 | "allowJs": true, 19 | "allowSyntheticDefaultImports": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "moduleResolution": "node", 22 | "resolveJsonModule": true, 23 | "isolatedModules": true, 24 | "noEmit": true, 25 | "plugins": [{"name": "typescript-plugin-css-modules"}] 26 | }, 27 | "include": [ 28 | "src/**/*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | resolve: { 5 | fallback: { 6 | path: require.resolve('path-browserify') 7 | } 8 | } 9 | }; -------------------------------------------------------------------------------- /logos/logo+tornjak.1675x700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.1675x700.png -------------------------------------------------------------------------------- /logos/logo+tornjak.2132x1291.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.2132x1291.png -------------------------------------------------------------------------------- /logos/logo+tornjak.282x340.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.282x340.png -------------------------------------------------------------------------------- /logos/logo+tornjak.3300x1112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.3300x1112.jpg -------------------------------------------------------------------------------- /logos/logo+tornjak.black.1808x791.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.black.1808x791.png -------------------------------------------------------------------------------- /logos/logo+tornjak.black.2166x1297.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.black.2166x1297.png -------------------------------------------------------------------------------- /logos/logo+tornjak.black.3300x1112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.black.3300x1112.png -------------------------------------------------------------------------------- /logos/logo+tornjak.black.3304x1114.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo+tornjak.black.3304x1114.jpg -------------------------------------------------------------------------------- /logos/logo.678x704.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo.678x704.png -------------------------------------------------------------------------------- /logos/logo.858x944.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/logo.858x944.png -------------------------------------------------------------------------------- /logos/tornjak_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spiffe/tornjak/b66ece79ac28927b45b271be3a2d867891fad433/logos/tornjak_logo.jpg -------------------------------------------------------------------------------- /pkg/agent/authentication/authenticator/authenticator.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/spiffe/tornjak/pkg/agent/authentication/user" 7 | ) 8 | 9 | type Authenticator interface { 10 | // AuthenticateRequest takes request, verifies certain properties 11 | // and returns relevant UserInfo to be interpreted by the Authorizer 12 | // or error upon verification error 13 | AuthenticateRequest(r *http.Request) *user.UserInfo 14 | } 15 | -------------------------------------------------------------------------------- /pkg/agent/authentication/authenticator/null_authenticator.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/spiffe/tornjak/pkg/agent/authentication/user" 7 | ) 8 | 9 | type NullAuthenticator struct{} 10 | 11 | func NewNullAuthenticator() *NullAuthenticator { 12 | return &NullAuthenticator{} 13 | } 14 | func (a *NullAuthenticator) AuthenticateRequest(r *http.Request) *user.UserInfo { 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/agent/authentication/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | type UserInfo struct { 4 | AuthenticationError error 5 | Roles []string 6 | } 7 | -------------------------------------------------------------------------------- /pkg/agent/authorization/authorization.go: -------------------------------------------------------------------------------- 1 | package authorization 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/spiffe/tornjak/pkg/agent/authentication/user" 7 | ) 8 | 9 | type Authorizer interface { 10 | // Authorize Request 11 | AuthorizeRequest(r *http.Request, u *user.UserInfo) error 12 | } 13 | -------------------------------------------------------------------------------- /pkg/agent/authorization/null_authorizer.go: -------------------------------------------------------------------------------- 1 | package authorization 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/spiffe/tornjak/pkg/agent/authentication/user" 7 | ) 8 | 9 | type NullAuthorizer struct{} 10 | 11 | func NewNullAuthorizer() *NullAuthorizer { 12 | return &NullAuthorizer{} 13 | } 14 | func (a *NullAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) error { 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/agent/authorization/rbac.go: -------------------------------------------------------------------------------- 1 | package authorization 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "net/http" 6 | 7 | "github.com/spiffe/tornjak/pkg/agent/authentication/user" 8 | ) 9 | 10 | type RBACAuthorizer struct { 11 | name string 12 | roleList map[string]string 13 | apiV1Mapping map[string]map[string][]string 14 | } 15 | 16 | // TODO put this in a common constants file 17 | var staticAPIV1List = map[string]map[string]struct{}{ 18 | "/api/v1/spire/serverinfo" :{"GET": {}}, 19 | "/api/v1/spire/healthcheck" :{"GET": {}}, 20 | "/api/v1/spire/entries" :{"GET": {}, "POST": {}, "DELETE": {}}, 21 | "/api/v1/spire/agents" :{"GET": {}, "POST": {}, "DELETE": {}}, 22 | "/api/v1/spire/agents/ban" :{"POST": {}}, 23 | "/api/v1/spire/agents/jointoken" :{"POST": {}}, 24 | "/api/v1/tornjak/clusters" :{"GET": {}, "POST": {}, "PATCH": {}, "DELETE": {}}, 25 | "/api/v1/tornjak/selectors" :{"GET": {}, "POST": {}}, 26 | "/api/v1/tornjak/agents" :{"GET": {}}, 27 | "/api/v1/tornjak/serverinfo" :{"GET": {}}, 28 | "/api/v1/spire/bundle" :{"GET": {}}, 29 | "/api/v1/spire/federations/bundles" :{"GET": {}, "POST": {}, "DELETE": {}, "PATCH": {}}, 30 | } 31 | 32 | func validateInitParameters(roleList map[string]string, apiV1Mapping map[string]map[string][]string) error { 33 | if roleList == nil { 34 | return errors.Errorf("No roles defined") 35 | } 36 | for path, method_dict := range apiV1Mapping { 37 | for method, allowList := range method_dict { 38 | // check that API exists 39 | if _, ok := staticAPIV1List[path][method]; !ok { 40 | return errors.Errorf("API V1 path %s does not exist with method %s", path, method) 41 | } 42 | 43 | // check that each role exists in roleList 44 | for _, allowedRole := range allowList { 45 | if _, ok := roleList[allowedRole]; !ok { 46 | return errors.Errorf("API V1 %s lists undefined role %s", path, allowedRole) 47 | } 48 | } 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | func NewRBACAuthorizer(policyName string, roleList map[string]string, apiV1Mapping map[string]map[string][]string) (*RBACAuthorizer, error) { 55 | err := validateInitParameters(roleList, apiV1Mapping) 56 | if err != nil { 57 | return nil, errors.Errorf("Could not parse policy %s: invalid mapping: %v", policyName, err) 58 | } 59 | return &RBACAuthorizer{ 60 | name: policyName, 61 | roleList: roleList, 62 | apiV1Mapping: apiV1Mapping, 63 | }, nil 64 | } 65 | 66 | func (a *RBACAuthorizer) authorizeAPIV1Request(r *http.Request, u *user.UserInfo) error { 67 | userRoles := u.Roles 68 | apiPath := r.URL.Path 69 | apiMethod := r.Method 70 | 71 | allowedRoles := a.apiV1Mapping[apiPath][apiMethod] 72 | 73 | // if no role listed for api, reject 74 | if len(allowedRoles) == 0 { 75 | return errors.New("Unauthorized request") 76 | } 77 | 78 | // check each allowed role 79 | for _, allowedRole := range allowedRoles { 80 | if allowedRole == "" { // all authenticated allowed 81 | return nil 82 | } 83 | for _, role := range userRoles { 84 | // user has role 85 | if role == allowedRole { 86 | return nil 87 | } 88 | } 89 | } 90 | return errors.New("Unauthorized Request") 91 | } 92 | 93 | func (a *RBACAuthorizer) AuthorizeRequest(r *http.Request, u *user.UserInfo) error { 94 | // if not authenticated fail and return error 95 | if u.AuthenticationError != nil { 96 | return errors.Errorf("Authentication error: %v", u.AuthenticationError) 97 | } 98 | 99 | // if not authorized fail and return error 100 | err := a.authorizeAPIV1Request(r, u) 101 | if err != nil { 102 | return errors.Errorf("Tornjak API V1 Authorization error: %v", err) 103 | } 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /pkg/agent/authorization/rbac_test.go: -------------------------------------------------------------------------------- 1 | package authorization 2 | 3 | import ( 4 | "testing" 5 | "strings" 6 | //"github.com/spiffe/tornjak/pkg/agent/authentication/user" 7 | ) 8 | 9 | func TestNewRBACAuthorizer(t *testing.T) { 10 | // INIT failures 11 | // fail when no roles defined 12 | _, err := NewRBACAuthorizer("", nil, nil) 13 | if err == nil { 14 | t.Fatal("ERROR: successfully initialized RBAC without roles") 15 | } 16 | 17 | // constants for 4 parameters 18 | policyName := "testPolicy" 19 | roleList_1 := map[string]string{"admin": "admin"} 20 | apiV1Mapping_1 := map[string]map[string][]string{"/api/v1/spire/serverinfo": {"GET": {"admin", "viewer"}}} 21 | 22 | roleList_2 := map[string]string{"admin": "admin", "viewer": "viewer"} 23 | apiV1Mapping_2 := apiV1Mapping_1 24 | 25 | roleList_3 := map[string]string{"admin": "admin", "user": "user"} 26 | apiV1Mapping_3 := apiV1Mapping_1 27 | 28 | roleList_4 := roleList_2 29 | apiV1Mapping_4 := map[string]map[string][]string{"/api/v1/unknown/serverinfo": {"GET": {"admin", "viewer"}}} 30 | 31 | roleList_5 := roleList_2 32 | apiV1Mapping_5 := map[string]map[string][]string{"/api/v1/spire/serverinfo": {"POST": {"admin", "viewer"}}} 33 | 34 | // fail when roles in apiMapping not in roleList 35 | _, err = NewRBACAuthorizer(policyName, roleList_1, apiV1Mapping_1) 36 | expectedErr := "Could not parse policy testPolicy: invalid mapping: API V1 /api/v1/spire/serverinfo lists undefined role viewer" 37 | if err == nil { 38 | t.Fatal("ERROR: successfully initialized RBAC without roles") 39 | } else if err.Error() != expectedErr { 40 | t.Fatalf("ERROR: expected %s, got %s", expectedErr, err.Error()) 41 | } 42 | 43 | // pass when roles in apiMapping in roleList 44 | _, err = NewRBACAuthorizer(policyName, roleList_2, apiV1Mapping_2) 45 | if err != nil { 46 | t.Fatalf("ERROR: failed to initialize RBAC: %s", err.Error()) 47 | } 48 | 49 | // fail when typo in apiMapping 50 | _, err = NewRBACAuthorizer(policyName, roleList_3, apiV1Mapping_3) 51 | if err == nil { 52 | t.Fatalf("expected an error but got nil") 53 | } 54 | expectedPhrase := "undefined role viewer" 55 | if !strings.Contains(err.Error(), expectedPhrase) { 56 | t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) 57 | } 58 | 59 | // fail when apiV1Mapping has path not in staticAPIV1List 60 | _, err = NewRBACAuthorizer(policyName, roleList_4, apiV1Mapping_4) 61 | if err == nil { 62 | t.Fatal("ERROR: successfully initialized RBAC without roles") 63 | } 64 | expectedPhrase = "/api/v1/unknown/serverinfo does not exist" 65 | if !strings.Contains(err.Error(), expectedPhrase) { 66 | t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) 67 | } 68 | 69 | // fail when apiV1Mapping has method not in staticAPIV1List 70 | _, err = NewRBACAuthorizer(policyName, roleList_5, apiV1Mapping_5) 71 | if err == nil { 72 | t.Fatal("ERROR: successfully initialized RBAC without roles") 73 | } 74 | expectedPhrase = "does not exist with method POST" 75 | if !strings.Contains(err.Error(), expectedPhrase) { 76 | t.Fatalf("expected error to contain %q but got %q", expectedPhrase, err.Error()) 77 | } 78 | 79 | } 80 | // func TestAuthorizeRequest(t *testing.T) { 81 | -------------------------------------------------------------------------------- /pkg/agent/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/spiffe/tornjak/pkg/agent/types" 5 | ) 6 | 7 | type AgentDB interface { 8 | // AGENT - SELECTOR/PLUGIN interface 9 | CreateAgentEntry(sinfo types.AgentInfo) error 10 | GetAgentSelectors() (types.AgentInfoList, error) 11 | GetAgentPluginInfo(name string) (types.AgentInfo, error) 12 | 13 | // CLUSTER interface 14 | GetClusters() (types.ClusterInfoList, error) 15 | CreateClusterEntry(cinfo types.ClusterInfo) error 16 | EditClusterEntry(cinfo types.ClusterInfo) error 17 | DeleteClusterEntry(name string) error 18 | 19 | // AGENT - CLUSTER Get interface (for testing)e 20 | GetAgentClusterName(spiffeid string) (string, error) 21 | GetClusterAgents(name string) ([]string, error) 22 | GetAgentsMetadata(req types.AgentMetadataRequest) (types.AgentInfoList, error) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/agent/db/sqlite_errors.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // SQLError is an error where the input appears correct but the database acts up 8 | type SQLError struct { 9 | Cmd string 10 | Err error 11 | } 12 | 13 | func (e SQLError) Error() string { 14 | return fmt.Sprintf("Unable to execute SQL query %v: %v", e.Cmd, e.Err.Error()) 15 | } 16 | 17 | // GetError is an error intended to signify something wrong with a get request 18 | // For example, non-existence 19 | type GetError struct { 20 | Message string 21 | } 22 | 23 | func (e GetError) Error() string { 24 | return e.Message 25 | } 26 | 27 | // PostFailure is meant to signify when the state of the database has not changed 28 | type PostFailure struct { 29 | Message string 30 | } 31 | 32 | func (e PostFailure) Error() string { 33 | return e.Message 34 | } 35 | -------------------------------------------------------------------------------- /pkg/agent/spirecrd/manager.go: -------------------------------------------------------------------------------- 1 | package spirecrd 2 | 3 | import ( 4 | "fmt" 5 | "k8s.io/client-go/rest" 6 | "k8s.io/client-go/dynamic" 7 | ) 8 | 9 | // CRDManager defines the interface for managing CRDs 10 | type CRDManager interface { 11 | // TODO add Create/Update/Delete functions for Federation CRD 12 | // ListClusterFederatedTrustDomain has the same signature as spire api 13 | ListClusterFederatedTrustDomains(ListFederationRelationshipsRequest) (ListFederationRelationshipsResponse, error) 14 | BatchCreateClusterFederatedTrustDomains(BatchCreateFederationRelationshipsRequest) (BatchCreateFederationRelationshipsResponse, error) 15 | } 16 | 17 | type SPIRECRDManager struct { 18 | className string 19 | kubeClient *dynamic.DynamicClient 20 | } 21 | 22 | // NewSPIRECRDManager initializes new SPIRECRDManager 23 | func NewSPIRECRDManager(className string) (*SPIRECRDManager, error) { 24 | // assume in-cluster 25 | config, err := rest.InClusterConfig() 26 | if err != nil { 27 | return nil, fmt.Errorf("error with in-cluster config: %v", err) 28 | } 29 | // create client 30 | kubeClient, err := dynamic.NewForConfig(config) 31 | if err != nil { 32 | return nil, fmt.Errorf("error creating kube client: %v", err) 33 | } 34 | 35 | return &SPIRECRDManager{ 36 | className: className, 37 | kubeClient: kubeClient, 38 | }, nil 39 | } 40 | 41 | -------------------------------------------------------------------------------- /pkg/agent/types/agentinfo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // AgentInfo contains the information about agents workload attestor plugin 4 | type AgentInfo struct { 5 | Spiffeid string `json:"spiffeid"` 6 | Plugin string `json:"plugin"` 7 | Cluster string `json:"cluster"` 8 | } 9 | 10 | // AgentInfoList contains the information about agents workload attestor plugin 11 | type AgentInfoList struct { 12 | Agents []AgentInfo `json:"agents"` 13 | } 14 | 15 | // AgentEntries contains agent spiffeid and list of spiffeids of Entries 16 | type AgentEntries struct { 17 | Spiffeid string `json:"spiffeid"` 18 | EntriesList []string `json:"entries_list"` 19 | } 20 | 21 | // AllAgentEntries contains a list of agent entry spiffeids 22 | type AllAgentEntries struct { 23 | Agents []AgentEntries `json:"agents"` 24 | } 25 | 26 | // AgentMetadataRequest contains a list of spiffeids 27 | type AgentMetadataRequest struct { 28 | Agents []string `json:"agents"` 29 | } 30 | -------------------------------------------------------------------------------- /pkg/agent/types/clusterinfo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // ClusterInfo contains the meta-information about clusters 4 | // TODO include details field for extra info/tags in json format (probably a byte array) 5 | type ClusterInfo struct { 6 | Name string `json:"name"` 7 | EditedName string `json:"editedName"` 8 | CreationTime string `json:"creationTime"` 9 | DomainName string `json:"domainName"` 10 | ManagedBy string `json:"managedBy"` 11 | PlatformType string `json:"platformType"` 12 | AgentsList []string `json:"agentsList"` 13 | } 14 | 15 | type ClusterInput struct { 16 | ClusterInstance ClusterInfo `json:"cluster"` 17 | } 18 | 19 | // ClusterInfoList contains the meta-information about clusters 20 | type ClusterInfoList struct { 21 | Clusters []ClusterInfo `json:"clusters"` 22 | } 23 | -------------------------------------------------------------------------------- /pkg/manager/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/spiffe/tornjak/pkg/manager/types" 5 | ) 6 | 7 | type ManagerDB interface { 8 | CreateServerEntry(sinfo types.ServerInfo) error 9 | GetServers() (types.ServerInfoList, error) 10 | GetServer(name string) (types.ServerInfo, error) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/manager/db/sqlite.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/mattn/go-sqlite3" 7 | "github.com/pkg/errors" 8 | 9 | "github.com/spiffe/tornjak/pkg/manager/types" 10 | ) 11 | 12 | // TO DO: Add DELETE servers option from the data base 13 | const ( 14 | initServersTable = "CREATE TABLE IF NOT EXISTS servers (servername TEXT PRIMARY KEY, address TEXT, tls bool, mtls bool, ca varBinary, cert varBinary, key varBinary)" 15 | ) 16 | 17 | type LocalSqliteDb struct { 18 | database *sql.DB 19 | } 20 | 21 | func NewLocalSqliteDB(dbpath string) (ManagerDB, error) { 22 | database, err := sql.Open("sqlite3", dbpath) 23 | if err != nil { 24 | return nil, errors.New("Unable to open connection to DB") 25 | } 26 | // Table for servers 27 | statement, err := database.Prepare(initServersTable) 28 | if err != nil { 29 | return nil, errors.Errorf("Unable to execute SQL query :%v", initServersTable) 30 | } 31 | _, err = statement.Exec() 32 | if err != nil { 33 | return nil, errors.Errorf("Unable to execute SQL query :%v", initServersTable) 34 | } 35 | 36 | return &LocalSqliteDb{ 37 | database: database, 38 | }, nil 39 | } 40 | 41 | func (db *LocalSqliteDb) CreateServerEntry(sinfo types.ServerInfo) error { 42 | statement, err := db.database.Prepare("INSERT INTO servers (servername, address, tls, mtls, ca, cert, key) VALUES (?,?,?,?,?,?,?)") 43 | if err != nil { 44 | return errors.Errorf("Unable to execute SQL query: %v", err) 45 | } 46 | _, err = statement.Exec(sinfo.Name, sinfo.Address, sinfo.TLS, sinfo.MTLS, sinfo.CA, sinfo.Cert, sinfo.Key) 47 | 48 | return err 49 | } 50 | 51 | func (db *LocalSqliteDb) GetServers() (types.ServerInfoList, error) { 52 | rows, err := db.database.Query("SELECT servername, address, tls, mtls, ca, cert, key FROM servers") 53 | if err != nil { 54 | return types.ServerInfoList{}, errors.New("Unable to execute SQL query") 55 | } 56 | 57 | sinfos := []types.ServerInfo{} 58 | var ( 59 | name string 60 | address string 61 | tls bool 62 | mtls bool 63 | ca []byte 64 | cert []byte 65 | key []byte 66 | ) 67 | for rows.Next() { 68 | if err = rows.Scan(&name, &address, &tls, &mtls, &ca, &cert, &key); err != nil { 69 | return types.ServerInfoList{}, err 70 | } 71 | 72 | sinfos = append(sinfos, types.ServerInfo{ 73 | Name: name, 74 | Address: address, 75 | TLS: tls, 76 | MTLS: mtls, 77 | CA: ca, 78 | Cert: cert, 79 | Key: key, 80 | }) 81 | } 82 | 83 | return types.ServerInfoList{ 84 | Servers: sinfos, 85 | }, nil 86 | } 87 | 88 | func (db *LocalSqliteDb) GetServer(name string) (types.ServerInfo, error) { 89 | row := db.database.QueryRow("SELECT servername, address, tls, mtls, ca, cert, key FROM servers WHERE servername=?", name) 90 | 91 | sinfo := types.ServerInfo{} 92 | err := row.Scan(&sinfo.Name, &sinfo.Address, &sinfo.TLS, &sinfo.MTLS, &sinfo.CA, &sinfo.Cert, &sinfo.Key) 93 | if err != nil { 94 | return types.ServerInfo{}, err 95 | } 96 | 97 | return sinfo, nil 98 | } 99 | -------------------------------------------------------------------------------- /pkg/manager/db/sqlite_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/spiffe/tornjak/pkg/manager/types" 8 | ) 9 | 10 | func cleanup() { 11 | _ = os.Remove("./local-test-db") 12 | } 13 | 14 | func TestServerCreate(t *testing.T) { 15 | defer cleanup() 16 | db, err := NewLocalSqliteDB("./local-test-db") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | sList, err := db.GetServers() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if len(sList.Servers) > 0 { 26 | t.Fatal("Server list should initially be empty") 27 | } 28 | 29 | sinfo := types.ServerInfo{ 30 | Name: "my-server", 31 | Address: "http://localhost:10000", 32 | } 33 | 34 | err = db.CreateServerEntry(types.ServerInfo{ 35 | Name: "my-server", 36 | Address: "http://localhost:10000", 37 | }) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | sList, err = db.GetServers() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | if len(sList.Servers) != 1 || sList.Servers[0].Name != sinfo.Name { 47 | t.Fatal("Server list should initially be empty") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/manager/types/serverinfo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "github.com/pkg/errors" 7 | "net/http" 8 | ) 9 | 10 | func (s ServerInfo) HttpClient() (*http.Client, error) { 11 | if s.TLS || s.MTLS { 12 | // Create a CA certificate pool and add cert.pem to it 13 | caCert := s.CA 14 | 15 | // TODO: allow use of generic cert authorities 16 | if len(s.CA) == 0 { 17 | return nil, errors.New("Cannot configure TLS if CA is not provided") 18 | } 19 | caCertPool := x509.NewCertPool() 20 | caCertPool.AppendCertsFromPEM(caCert) 21 | 22 | var mTLSCerts []tls.Certificate = nil 23 | if s.MTLS { 24 | // TODO: Add ability to support different cert for mTLS 25 | if len(s.Cert) == 0 || len(s.Key) == 0 { 26 | return nil, errors.New("Cannot configure MTLS if not key or cert is provided") 27 | } 28 | cert, err := tls.X509KeyPair(s.Cert, s.Key) 29 | if err != nil { 30 | return nil, err 31 | } 32 | mTLSCerts = []tls.Certificate{cert} 33 | } 34 | 35 | // Create a HTTPS client and supply the created CA pool 36 | client := &http.Client{ 37 | Transport: &http.Transport{ 38 | TLSClientConfig: &tls.Config{ 39 | RootCAs: caCertPool, 40 | Certificates: mTLSCerts, 41 | }, 42 | }, 43 | } 44 | 45 | return client, nil 46 | } 47 | 48 | // default to no TLS 49 | return &http.Client{}, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/manager/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // ServerInfo contains the information about servers 4 | type ServerInfo struct { 5 | Name string `json:"name"` 6 | Address string `json:"address"` 7 | TLS bool `json:"tls"` 8 | MTLS bool `json:"mtls"` 9 | CA []byte `json:"ca,omitempty"` 10 | Cert []byte `json:"cert,omitempty"` 11 | Key []byte `json:"key,omitempty"` 12 | } 13 | 14 | // ServerInfo contains the information about servers 15 | type ServerInfoList struct { 16 | Servers []ServerInfo `json:"servers"` 17 | } 18 | -------------------------------------------------------------------------------- /scripts/run_backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "${@}" 3 | 4 | # run serverinfo to print SPIRE config if given and Tornjak config 5 | /opt/tornjak/tornjak-backend ${@} serverinfo 6 | 7 | # run Tornjak server 8 | /opt/tornjak/tornjak-backend ${@} http 9 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | v2.2.0 2 | --------------------------------------------------------------------------------